Background
In this report we will be delving into understanding
survClust via simulating various scenarios in the
form of datasets. The choice of using simulated datasets makes the
context of data neutral and the findings from survClust more accessible.
We will go over outputs from survClust on various scenarios highlighting
the complex nature of time-event data and performance of survClust.
Note that, in this report my goal is to get you excited about
survClust and to walk you through the rigorous testing performed via
simulations and design of simulations to iron out the details of the
methodology.
For a deeper dive, please refer to the published manuscript with Genome
Medicine. survClust can be downloaded from my GitHub repository

What is survClust?
Until now, sub-typing in cancer biology has relied heavily upon
clustering/mining of molecular data alone, or unsupervised clustering of
biological sequencing data such as gene expression data, DNA copy number
data, gene mutation status etc . While this approach has been a cornerstone
in understanding cancer disease biology, when the ultimate goal is to
understand patient survival, a more focused approach with survival
outcome is desired.
survClust is an outcome weighted integrative supervised clustering
algorithm, designed to classify patients according to their molecular as
well as time-event data or end point of interest such as Overall
Survival (OS), Progression Free Survival (PFS) etc.
Below, we describe various scenarios where a supervised clustering
approach such as survClust will be desired than more traditional
unsupervised clustering approach such as kmeans.
Also, here is a quick primer
by my colleague on survival data analysis.
Applications of survClust
Applications of survClust in cancer genomics have been established in
previous work. See the list of scientific literature citing survClust
A possible extension could be say, we observe the life of Apple
MacBook Pros purchased in the year 2012 from years 2013-2018. We define
an event as a laptop failing entirely (coded as 1) where it couldn’t be
revived by an Apple genius. If the MacBook is still functioning after
2018, we consider it as a non event, (coded as 0). If a MacBoook Pro
drops from a study, or we are unable to keep track of their status aka
loss of follow up, we will classify this event as censored.
Apart from this we also collect various features after 1yr of usage -
battery health, processor chip details, number of alive pixels on the
screen, tests that could measure performance etc.
We are interested in seeing if we can identify batches of MacBook
Pros that have varied longevity (good or bad) based on the features that
we collected.
This is a very broad example and a proper study design might require
a deep dive into understanding more about Apple MacBook Pros. For
example, definition of an event is crucial when analyzing a study with
time-event data.
survClust workflow - the tldr version
survClust is available as an R package and is under the submission
process with Bioconductor.
Below we describe the proposed workflow of survClust method:

getDist - Compute a weighted distance matrix based on
outcome across given m data types. Standardization across
data types (binary, continuous) to facilitate the integration process
and accounting for non-overlapping samples is also accomplished in this
step.
combineDist - Integrate m data types by
averaging over m weighted distance matrices.
survClust and cv_survclust - Cluster
integrated weighted distance matrices via survClust.
Optimal k is estimated via cross-validation using
cv_survclust. Cross-validated results are assessed over the
following performance metrics - the logrank statistic,
standardized pooled within-cluster sum of squares (SPWSS) and
small cluster solutions with size less than 5
samples.
Note:
The input datatypes needs to be carefully pre-processed. See more
details under the supplementary section of the manuscript.
cv_survclust is a wrapper function that
cross-validates and outputs cluster assignments. If you run without
cross validation and just the commands on its own (getDist,
combineDist and survClust), you are
over-fitting!. Running multiple (at least 10) rounds of
cross validation is strongly recommended to arrive at a stable solution.
Also, the cross-validation step is computationally intensive and one can
reduce run times by using parallel computing.
Read more about the performance metrics here and how to chose an optimal
k
Logrank test statistic
The logrank test statistic is based on a non-parametric approach that
quantifies survival difference between resulting subtypes and makes no
assumption about the survival distributions.It tests the null hypothesis
that there is no difference in survival between the groups.
The optimal k is the one with the maximum logrank statistic or at an
inflection point where the statistic plateaus.
SPWSS
SPWSS is calculated as the pooled within-cluster sum of squares and
standardized by the total sum of squares similar to methodology used in
the gap statistic to select the appropriate number of
clusters.
The optimal number of clusters is where SPWSS is minimized and
creates an “elbow” or a point of inflection, where addition of more
clusters does not improve cluster separation. Since, this metric is
standardized (lies between 0-1), it can also be used to compare
individual runs of datasets that are measured over same samples.
Cluster Size
This metric computes the cluster size for each k across
rounds of cross-validation. It records the instances where a round of
cross validation returned a cluster size with less than 5 subjects. Such
a solution might be unreliable and perhaps over-fitted for that
k. An optimal k should have the least amount
of such instances across the total cross validation rounds.
Data scenarios
Below we simulate 3 data scenarios to highlight the functionality of
survClust. Survival times for demonstration purposes will also be
simulated where we assume they are derived from an exponential
distribution. Also, note that in addition to simulated dataset being
context free, we can also predetermine a ground truth that could give us
a fair estimate of our method’s performance.
A handy function to simulate input data and survival data is provided
- simulate_survclust_data_fun.R.
Scenario 1
Here we will simulate a dataset with three clusters, a total of 150
(\(c_1=50, c_2=50, c_3=50\)) samples
and 150 features such that:
\(c_1 \sim N(\mu =
-1.5,\sigma=1)\)
\(c_2 \sim N(\mu = 0, \sigma=1)\)
and
\(c_3 \sim N(\mu = 1.5,
\sigma=1)\)
Note that, not all 150 features will be informative and we will only
simulate about 15 (10%) features that will really drive the distinction
between clusters. This is especially true in the context of human genome
with about ~19,000 genes, where only a few of the genes will be actually
associated with the disease biology and survival.
These clusters will also have distinct median survival of 4.5, 3.25,
and 2 yrs respectively.
More realistically, from the features that are distinct some are
going to be associated with survival and while others remain distinct
with no association to survival. We will simulate half of the
informative features as distinct and associated with survival and
remaining half as just distinct.
#simulating scenario 1
mat_scenario1 <- list()
#fixing the solution to simulated scenario1
true_soln_scenario1 <- data.frame(samples = paste0("S",1:150) ,
cluster = c(rep(1,50), rep(2,50), rep(3,50)))
#function from simulate_survclust_data_fun.R to simulate input data
mat_scenario1[[1]] <- simulate_data_type(k=3,
k_size = rep(50,3), n_features = 150,
mu = c(-1.5, 0, 1.5), sd = rep(1,3),
perc_inform = 0.10, my_seed = 112,
permute = TRUE)
#simulating survival data
surv_scenario1 <- simulate_surv_dat(k=3,
k_size = rep(50,3),
med_surv_times = c(4.5, 3.25, 2),
max_survival = 10, my_seed = 112)
Below we plot the distribution of our simulated 3-class clusters and
the survival curves in a Kaplan Meier plot.
#long format to wrangle dta to plot ggridges and join the truth
mat_scenario1_long <- mat_scenario1[[1]] %>%
as.data.frame() %>%
tibble::rownames_to_column(var = "samples") %>%
tidyr::pivot_longer(., cols = -samples, names_to = "features") %>%
left_join(., true_soln_scenario1, by ="samples") %>%
mutate(cluster = as.factor(cluster))
#input data plot
p1 = ggplot(mat_scenario1_long, aes(x = value, y = cluster)) + geom_density_ridges(aes(fill = cluster), scale = 2, alpha = 0.6) + theme_bw() + scale_fill_manual(values = my_col_pal[1:3]) + ggtitle("cluster distribution of k=3 clusters, scenario1")
#KM plot
p2 = surv_scenario1 %>%
tibble::rownames_to_column(var = "samples") %>%
left_join(., true_soln_scenario1, by ="samples") %>%
ggsurvplot(survfit(Surv(time, event) ~ cluster, data = .), data = .,
surv.median.line = "v", palette = my_col_pal) +
ggtitle("survival distribution across \n k=3 clusters, scenario1")
ggarrange(p1, (p2$plot + theme(plot.title = element_text(size = 13))), nrow=1)
# Picking joint bandwidth of 0.16

We see that since only 10% of the features are informative and 90%
are noise, the overall distribution appears to be uniform for the three
clusters.
The dashed vertical line from each survival curve represents the
median survival rate for each group. See below the actual fit from
survfit, as compared what we actually used in out
simulation, \(\lambda_{c1} = 4.5,
\lambda_{c2}=3.25, \lambda_{c3} = 2\)
#survfit to compute median survival times
fit <- surv_scenario1 %>%
tibble::rownames_to_column(var = "samples") %>%
left_join(., true_soln_scenario1, by ="samples") %>%
survfit(Surv(time, event) ~ cluster, data = .)
fit_median <- summary(fit)$table[,'median']
data.frame(cluster = gsub("cluster=","c",names(fit_median)),
`median survival(yrs)` = round(fit_median,2)) %>%
gt::gt(caption = "median survival times are from `survfit` fit")
median survival times are from `survfit` fit
| cluster |
median.survival.yrs. |
| c1 |
4.50 |
| c2 |
3.34 |
| c3 |
2.00 |
survClust run
Once the simulated data is assembled, we proceed to running survClust
via cv_curvclust. Each k will be
cross-validated across 3-folds for 10 rounds where fresh resampling for
folds is done at every round. Computation across each k
(where k = 2 to 7 clusters) will be parallelized using
dopar from package doParallel.
This is wrapped up in a function by the name of
sim_cvrounds, Expand the code section below.
sim_cvrounds<-function(kk, simdat, simsurvdat, fold=3, cv_rounds=10){
this.fold<-fold
fit<-list()
for (i in seq_len(cv_rounds)){
fit[[i]] <- survClust::cv_survclust(simdat, simsurvdat,kk,this.fold)
}
return(fit)
}
registerDoParallel(cl <- makeCluster(6))
ptm <- Sys.time()
#each core performs computation for each k cluster
sim1_cv_fit <- foreach(kk=2:7) %dopar% sim_cvrounds(kk, simdat = mat_scenario1, simsurvdat = surv_scenario1)
ptm2 <- Sys.time()
stopCluster(cl)
tt = ptm2-ptm
The cross validation took 0.77 minutes to run.
Results and choosing k
All cross validated rounds of desired k clusters is then evaluated
across 3 metrics -
- Logrank test statistic
- Standardized pooled within sum of squares (SPWSS)
- Number of reliable solutions
More details on the metrics can be found here
In the plot below, the top-left plot is summarizing logrank
test statistic over 10 rounds of cross-validated class labels
across 3-fold cross-validation. Each dot is a solution from a cross
validated round for that k. Here, we see that logrank peaks
at k=3.
The top-right plot is summarizing SPWSS or Standardized
Pooled Within Sum of Squares. It appears that SPWSS decreases
monotonically as the number of clusters k increases. The
optimal number of clusters is where SPWSS creates an “elbow” , here the
plot elbows at k=3
The last plot, on the bottom-left, shows for each k how
many k class labels have <=5 samples in 10
rounds of cross validation. In our case here, for k=3 the
number of classes with <=5 samples is zero.
ss_stats <- getStats(sim1_cv_fit, kk=7, cvr=10)
p_lr <- plotStats_tidy(ss_stats$lr, highlight_k = 3)
p_lr <- p_lr + ylab("logrank test statistic")
p_spwss <- plotStats_tidy(ss_stats$spwss, highlight_k = 3)
p_spwss <- p_spwss + ylab("SPWSS")
p_clusize <- data.frame(k = paste0("k",2:7), cv_less_than_5 = ss_stats$bad.sol) %>%
ggplot(aes(x=k, y = cv_less_than_5)) +
geom_line(col = "skyblue", group = "k") + geom_point() +
theme_minimal() + ylab("no. of cv solution with <=5 samples")
ggarrange(p_lr, p_spwss, p_clusize, nrow=2, ncol=2)

#cv_voting creates a consensus of all solutions across the 10 cross validated rounds
k3 <- cv_voting(cv.fit = sim1_cv_fit,
dat.dist = getDist(datasets = mat_scenario1,
survdat = surv_scenario1),
pick_k=3)
k=3
From the metrics above, we choose k=3 as our optimal k.
We can compare this with our simulated true solution -
#creating tab to store results
tab <- true_soln_scenario1 %>%
left_join(., data.frame(sim_soln = k3, samples = paste0("S", 1:150)),
by = "samples")
tab %>%
dplyr::select(-samples) %>%
dplyr::rename("truth" = cluster) %>%
gtsummary::tbl_summary(by = sim_soln) %>% modify_header(label = "**simulated solution**")
| simulated solution |
1, N = 53 |
2, N = 49 |
3, N = 48 |
| truth |
|
|
|
| 1 |
50 (94%) |
0 (0%) |
0 (0%) |
| 2 |
3 (5.7%) |
47 (96%) |
0 (0%) |
| 3 |
0 (0%) |
2 (4.1%) |
48 (100%) |
ari <- round(mclust::adjustedRandIndex(tab$sim_soln, tab$cluster),2)
The concordance between true and survClust predicted solution can be
computed by Rand Index.
The Rand Index is used to measure agreement between two
classification labels. It has a maximum of 1 and a minimum of 0. Here 0
means the two data labels have no shared information and 1 means they
are the same labels.
Conclusion
The concordance between the survClust reported solutions and truth
class labels is high 0.9, meaning survClust did a good job in recovering
the true solution when features carry a mixed signal. Also, note that,
even though only 10% of the features were informative, we split this in
half to simulate features that were distinct and associated with
survival and survClust was able to borrow from the 5% of the informative
features.
Scenario 2
survClust can perform clustering on more than one data type and can
output results by integrating the datasets. In genomic context, these
different datatypes are the different biological avenues such as gene
expression data (continuous), gene mutation status (binary) etc.
capturing different layers of the tumor biology.
Here we will simulate two dataset with three clusters, a total of 150
samples and 100 features with one dataset having strong clustering
information and the other having weak clustering information. See below
-
Dataset 1
Simulate three clusters (\(c_1=50, c_2=50,
c_3=50\)) such that
\(c_1 \sim N(\mu =
-1.5,\sigma=1)\),
\(c_2 \sim N(\mu = 0, \sigma=1)\)
and
\(c_3 \sim N(\mu = 1.5,
\sigma=1)\)
Dataset 2
Simulate three clusters (\(c_1=50, c_2=50,
c_3=50\)) such that -
\(c_1 \sim N(\mu =
-0.5,\sigma=1)\),
\(c_2 \sim N(\mu = 0, \sigma=1)\)
and
\(c_3 \sim N(\mu = 0.5,
\sigma=1)\)
Dataset2 can be interpreted as a modality that is adding noise to the
modality of the data that explains the true survival association. For
this scenario, the survival data remains same as scenario 1.
#scenario 2 set up
mat_scenario2 <- list()
true_soln_scenario2 <- true_soln_scenario1
#simulate data set but with strong cluster association
mat_scenario2[[1]] <- simulate_data_type(k=3,
k_size = rep(50,3),
n_features = 100,
mu = c(-1.5, 0, 1.5), sd = rep(1,3),
perc_inform = 0.10, my_seed = 123)
#simulate an additional data set but with weak cluster association
mat_scenario2[[2]] <- simulate_data_type(k=3,
k_size = rep(50,3),
n_features = 150,
mu = c(-0.5, 0, 0.5), sd = rep(1,3),
perc_inform = 0.10, my_seed = 123)
surv_scenario2 <- surv_scenario1
Below, we plot the distribution of only informative features across
clusters across the two datasets. The dashed grey lines represent the
means as we simulated across the two datasets.
#long form of dataset to visualize cluster distribution
#dataset1
mat_scenario2_dat1_long <- mat_scenario2[[1]] %>%
as.data.frame() %>%
tibble::rownames_to_column(var = "samples") %>%
tidyr::pivot_longer(., cols = -samples, names_to = "features") %>%
left_join(., true_soln_scenario2, by ="samples") %>%
mutate(cluster = as.factor(cluster))
p1 <- ggplot(mat_scenario2_dat1_long %>%
filter(features %in% paste0("F",1:10 )), aes(x = value, y = cluster)) +
geom_density_ridges(aes(fill = cluster), scale = 2, alpha = 0.6) + theme_bw() + scale_fill_manual(values = my_col_pal[1:3]) +
ggtitle("distribution of informative features only, dataset1, scenario2")
p1 <- p1 +
theme(plot.title = element_text(size=10)) +
geom_vline(xintercept = c(-1.5,0,1.5), col = "grey", linetype = "dashed")
#dataset2
mat_scenario2_dat2_long <- mat_scenario2[[2]] %>%
as.data.frame() %>%
tibble::rownames_to_column(var = "samples") %>%
tidyr::pivot_longer(., cols = -samples, names_to = "features") %>%
left_join(., true_soln_scenario2, by ="samples") %>%
mutate(cluster = as.factor(cluster))
p2 <- ggplot(mat_scenario2_dat2_long %>%
filter(features %in% paste0("F",1:10 )), aes(x = value, y = cluster)) +
geom_density_ridges(aes(fill = cluster), scale = 2, alpha = 0.6) + theme_bw() + scale_fill_manual(values = my_col_pal[1:3]) + ggtitle("distribution of informative features only, dataset2, scenario2")
p2 <- p2 +
theme(plot.title = element_text(size=10)) +
geom_vline(xintercept = c(-0.5,0,0.5), col = "grey", linetype = "dashed")
ggarrange(p1, p2, nrow=1)
# Picking joint bandwidth of 0.244
# Picking joint bandwidth of 0.244

survClust run
In this section we will be performing weighted integrative clustering
survClust via previously defined function sim_cvrounds.
registerDoParallel(cl <- makeCluster(6))
ptm <- Sys.time()
sim2_cv_fit <- foreach(kk=2:7) %dopar% sim_cvrounds(kk, simdat = mat_scenario2, simsurvdat = surv_scenario2)
ptm2 <- Sys.time()
stopCluster(cl)
tt = ptm2-ptm
The cross validation took 0.96 minutes to run.
Results and choosing k
In the plot below, the top-left plot summarizing logrank test
statistic over 10 rounds of cross-validated for each k, we see it is
maximum for k=3. This is in concordance with SPWSS, where
the point of inflection is also at k=3.
#code to see evaluate survClust run across logrank test stats, spwss
ss_stats <- getStats(sim2_cv_fit, kk=7, cvr=10)
p_lr <- plotStats_tidy(ss_stats$lr, highlight_k = 3)
p_lr <- p_lr + ylab("logrank test statistic")
p_spwss <- plotStats_tidy(ss_stats$spwss, highlight_k = 3)
p_spwss <- p_spwss + ylab("SPWSS")
p_clusize <- data.frame(k = paste0("k",2:7), cv_less_than_5 = ss_stats$bad.sol) %>%
ggplot(aes(x=k, y = cv_less_than_5)) + geom_point() + geom_line(col = "skyblue", group="k") + theme_minimal() + ylab("cv solution with <=5 samples")
ggarrange(p_lr, p_spwss, p_clusize, nrow=2, ncol=2)

k3 <- cv_voting(cv.fit = sim2_cv_fit,
dat.dist = getDist(datasets = mat_scenario2,
survdat = surv_scenario2),
pick_k=3)
k=3
From the metrics above, we choose k=3 as our optimal k.
We can compare this with our simulated true solution -
tab <- true_soln_scenario2 %>%
left_join(., data.frame(sim_soln = k3, samples = paste0("S", 1:150)),
by ="samples")
tab %>%
dplyr::select(-samples) %>%
dplyr::rename("truth" = cluster) %>%
gtsummary::tbl_summary(by = sim_soln) %>% modify_header(label = "**simulated solution**")
| simulated solution |
1, N = 49 |
2, N = 48 |
3, N = 53 |
| truth |
|
|
|
| 1 |
0 (0%) |
0 (0%) |
50 (94%) |
| 2 |
47 (96%) |
0 (0%) |
3 (5.7%) |
| 3 |
2 (4.1%) |
48 (100%) |
0 (0%) |
ari <- round(mclust::adjustedRandIndex(tab$sim_soln, tab$cluster),2)
The concordance between true and survClust predicted solution as
computed by Rand Index is 0.9
Conclusion
In this scenario we performed an integrative supervised
clustering. Note that, survClust can perform clustering on as many
datasets as possible and consisting a mix of binary and continuous data
types. In the published work, survClust performed integrative clustering
on 6 data types.
The Rand Index is between survClust solution and the truth is 0.9
meaning even upon addition of a weaker data set, survClust
is able to predict a solution close to truth without affecting its
performance in the presence of noise.
Scenario 3
In this last scenario, we simulate two datasets with 175 samples and
200 features such that -
Data type 1 – strong clusters and weak survival association.
Datatype 2 – weak clusters and strong cluster association.

mat_scenario3 <- list()
#dataset 1
mat_scenario3[[1]] <- simulate_data_type(k=3,
k_size = c(50,50,75),
n_features = 200,
mu = c(0,-1.5, 1.5), sd = rep(1,3),
perc_inform = 0.10)
#dataset 2
mat_scenario3[[2]] <- simulate_data_type(k=4,
k_size = c(50,50,30,45),
n_features = 200,
mu = c(0,-0.5, 0.5, 1.5), sd = rep(1,4),
perc_inform = 0.10)
#simulate survival data
surv_scenario3 <- simulate_surv_dat(k=4,
k_size = c(50,50,30,45),
med_surv_times = c(5.5,4,2.5,1),
max_survival = 10, my_seed=112)
#truth
true_soln_scenario3 <- data.frame(samples = paste0("S", 1:175),
cluster = c(rep(1,50), rep(2,50), rep(3,30), rep(4,45)))
Here the survival association is stronger with dataset2 and we
simulate a survival data with 4 clusters. Below is the simulated
survival distribution -
pp = surv_scenario3 %>%
tibble::rownames_to_column(var = "samples") %>%
left_join(., true_soln_scenario3, by ="samples") %>%
ggsurvplot(survfit(Surv(time, event) ~ cluster, data = .), data = .,
surv.median.line = "v", palette = my_col_pal) +
ggtitle("survival distribution across \n k=4 clusters, scenario3")
pp$plot + theme(plot.title = element_text(size = 11),
legend.text = element_text(size=6))

The median survival rates of the simulated survival dataset from
survfit model are below.
#median survival rate model fit
fit <- surv_scenario3 %>%
tibble::rownames_to_column(var = "samples") %>%
left_join(., true_soln_scenario3, by ="samples") %>%
survfit(Surv(time, event) ~ cluster, data = .)
fit_median <- summary(fit)$table[,'median']
data.frame(cluster = gsub("cluster=","c",names(fit_median)),
`median survival(yrs)` = round(fit_median,2)) %>%
gt::gt(caption = "median survival times are from `survfit` fit")
median survival times are from `survfit` fit
| cluster |
median.survival.yrs. |
| c1 |
5.65 |
| c2 |
4.11 |
| c3 |
2.70 |
| c4 |
1.08 |
survClust run
Similar, to previous section, cross validation was performed and
parallelized for each k.
registerDoParallel(cl <- makeCluster(6))
ptm <- Sys.time()
sim3_cv_fit <- foreach(kk=2:7) %dopar% sim_cvrounds(kk, simdat = mat_scenario3, simsurvdat = surv_scenario3)
ptm2 <- Sys.time()
stopCluster(cl)
tt = ptm2-ptm
The cross validation took 2.74 minutes to run.
Results and choosing k
The logrank test statistic for this scenario is maximum at
k=4 and we see that the SPWSS curve “elbows” at
k=4. The cross validation round of k=4 also
did not have any cluster sizes with <=5 samples. Thus, we will choose
k=4 as the optimal k.
ss_stats <- getStats(sim3_cv_fit, kk=7, cvr=10)
p_lr <- plotStats_tidy(ss_stats$lr, highlight_k = 4)
p_lr <- p_lr + ylab("logrank test statistic")
p_spwss <- plotStats_tidy(ss_stats$spwss, highlight_k = 4)
p_spwss <- p_spwss + ylab("SPWSS")
p_clusize <- data.frame(k = paste0("k",2:7), cv_less_than_5 = ss_stats$bad.sol) %>%
ggplot(aes(x=k, y = cv_less_than_5)) + geom_point() + geom_line(col = "skyblue", group="k") + theme_minimal() + ylab("cv solution with <=5 samples")
ggarrange(p_lr, p_spwss, p_clusize, nrow=2, ncol=2)

k4 <- cv_voting(cv.fit = sim3_cv_fit,
dat.dist = getDist(datasets = mat_scenario3,
survdat = surv_scenario3),
pick_k=4)
k=4
From the metrics above, we choose k=4 as our optimal k.
We can compare this with our simulated true solution -
tab <- true_soln_scenario3 %>%
left_join(., data.frame(sim_soln = k4, samples = paste0("S", 1:175)),
by = "samples")
tab %>%
dplyr::select(-samples) %>%
dplyr::rename("truth" = cluster) %>%
gtsummary::tbl_summary(by = sim_soln) %>% modify_header(label = "**simulated solution**")
| simulated solution |
1, N = 29 |
2, N = 50 |
3, N = 46 |
4, N = 50 |
| truth |
|
|
|
|
| 1 |
0 (0%) |
0 (0%) |
0 (0%) |
50 (100%) |
| 2 |
0 (0%) |
50 (100%) |
0 (0%) |
0 (0%) |
| 3 |
29 (100%) |
0 (0%) |
1 (2.2%) |
0 (0%) |
| 4 |
0 (0%) |
0 (0%) |
45 (98%) |
0 (0%) |
ari <- round(mclust::adjustedRandIndex(tab$sim_soln, tab$cluster),2)
The concordance between true and survClust predicted solution as
computed by Rand Index is 0.99
Conclusion
In this scenario we observe that survClust was able to arrive at
solution showing good concordance with the truth even when weaker
clusters show better association with survival.
Discussion
Here we demonstrated survClust, a supervised clustering methodology
that can incorporate survival information as weights but also integrate
data from multiple modalities. We looked at various scenarios and
highlighted how survClust was able to predict solutions in
high concordance with the true class labels that we simulated.
However, there were additional simulating strategies that one could
further apply -
At present a cluster contains features that are associated with
survival in one direction only. In more thorough work towards the final
publication, I simulated cluster structures that had features that had a
mix of features that contributed to worse and better survival
association.
Simulating survival distribution on a small sample size is
challenging and might violate the assumption of proportional hazards.
This could explain why for some scenarios we are misclassifying some
samples.
set.seed functionality provided via
my_seed parameter should be used carefully, as one might be
duplicating data while simulating. For example, if we want to simulate 6
clusters and out of which 2 are similar in size and distribution but
might vary in terms of survival, a case we often see with real
data.
survClust continues to be used by many researchers in the scientific
community.
Is integration always better?
survClust can be run individually in multiple datasets or in an
integrated fashion across different modalities profiled for constant
subjects. According to the nature and quality of these datasets, one
might perform better or worse by integrating all the available
information. We ran survClust on individual datasets from scenarios 2
and 3 and compare them with the integrated run as reported above.
registerDoParallel(cl <- makeCluster(6))
dat<-list()
dat[[1]] <- mat_scenario2[[1]]
sim2_cv_fit1 <- foreach(kk=2:7) %dopar% sim_cvrounds(kk, simdat = dat, simsurvdat = surv_scenario2)
dat<-list()
dat[[1]] <- mat_scenario2[[2]]
sim2_cv_fit2 <- foreach(kk=2:7) %dopar% sim_cvrounds(kk, simdat = dat, simsurvdat = surv_scenario2)
dat<-list()
dat[[1]] <- mat_scenario3[[1]]
sim3_cv_fit1 <- foreach(kk=2:7) %dopar% sim_cvrounds(kk, simdat = dat, simsurvdat = surv_scenario3)
dat<-list()
dat[[1]] <- mat_scenario3[[2]]
sim3_cv_fit2 <- foreach(kk=2:7) %dopar% sim_cvrounds(kk, simdat = dat, simsurvdat = surv_scenario3)
stopCluster(cl)
all_stats <- purrr::map(c("sim2_cv_fit1", "sim2_cv_fit2", "sim3_cv_fit1", "sim3_cv_fit2"), function(x) getStats(get(x), kk=7, cvr=10))
names(all_stats) = c("sim2_cv_fit1", "sim2_cv_fit2", "sim3_cv_fit1", "sim3_cv_fit2")
sim2_stats <- getStats(sim2_cv_fit, kk=7, cvr = 10)
sim3_stats <- getStats(sim3_cv_fit, kk=7, cvr=10)
scenario2_integ <- make_integrated_data(dat1 = all_stats$sim2_cv_fit1, dat2 = all_stats$sim2_cv_fit2, integ = sim2_stats)
scenario2_integ_plots <- make_integrated_plots(scenario2_integ)
pp <- ggarrange(plotlist = scenario2_integ_plots, ncol=1)
pp2 <- annotate_figure(pp, top = text_grob("Scenario2", face = "bold"))
scenario3_integ <- make_integrated_data(dat1 = all_stats$sim3_cv_fit1, dat2 = all_stats$sim3_cv_fit2, integ = sim3_stats)
scenario3_integ_plots <- make_integrated_plots(scenario3_integ)
pp <- ggarrange(plotlist = scenario3_integ_plots, ncol=1)
pp3 <- annotate_figure(pp, top = text_grob("Scenario3", face = "bold"))
ggarrange(pp2, pp3)

In the plots above, we plot the median value across cross validated
rounds for each k for that particular metric (logrank, SPWSS etc.). The
bars represent the 25th and 75th quantiles of the data.
In scenario 2, we added a noisy data type as dataset 2 and we see
that dataset 2 performs worse than integrated and dataset1. Dataset1
carried the strong cluster distinction and is doing as well as
integrated survClust solution in terms of peak logrank at
k=3.
A similar trend is seen for scenario 3, where dataset1 had strong
cluster separation but poor survival association with overall low median
logrank values. Whereas, integrated solution and dataset2 are performing
similar.
Overall, we see that integrated data type performs as well or
slightly better than the dataset carrying the strong association with
survival.
Session Info
#environemnt and package details
pander::pander(sessionInfo())
R version 4.3.3 (2024-02-29)
Platform: aarch64-apple-darwin20 (64-bit)
locale:
en_US.UTF-8||en_US.UTF-8||en_US.UTF-8||C||en_US.UTF-8||en_US.UTF-8
attached base packages: parallel,
stats, graphics, grDevices, utils,
datasets, methods and base
other attached packages:
survClust(v.0.99.8), gtsummary(v.1.7.2),
DT(v.0.33), ggridges(v.0.5.6),
doParallel(v.1.0.17), iterators(v.1.0.14),
foreach(v.1.5.2), survminer(v.0.4.9),
ggpubr(v.0.6.0), survival(v.3.5-8),
ggplot2(v.3.5.1), purrr(v.1.0.2),
tidyr(v.1.3.1) and dplyr(v.1.1.4)
loaded via a namespace (and not attached):
bitops(v.1.0-7), gridExtra(v.2.3),
rlang(v.1.1.3), magrittr(v.2.0.3),
matrixStats(v.1.3.0), compiler(v.4.3.3),
png(v.0.1-8), vctrs(v.0.6.5),
stringr(v.1.5.1), pkgconfig(v.2.0.3),
crayon(v.1.5.2), fastmap(v.1.1.1),
backports(v.1.4.1), XVector(v.0.42.0),
labeling(v.0.4.3), pander(v.0.6.5),
KMsurv(v.0.1-5), utf8(v.1.2.4),
rmarkdown(v.2.26), markdown(v.1.12),
ggbeeswarm(v.0.7.2), xfun(v.0.43),
MultiAssayExperiment(v.1.28.0), zlibbioc(v.1.48.2),
cachem(v.1.0.8), GenomeInfoDb(v.1.38.8),
jsonlite(v.1.8.8), highr(v.0.10),
DelayedArray(v.0.28.0), pdist(v.1.2.1),
broom(v.1.0.5), R6(v.2.5.1), bslib(v.0.7.0),
stringi(v.1.8.3), car(v.3.1-2),
GenomicRanges(v.1.54.1), jquerylib(v.0.1.4),
Rcpp(v.1.0.12), SummarizedExperiment(v.1.32.0),
knitr(v.1.45), zoo(v.1.8-12),
IRanges(v.2.36.0), Matrix(v.1.6-5),
splines(v.4.3.3), tidyselect(v.1.2.1),
rstudioapi(v.0.16.0), abind(v.1.4-5),
yaml(v.2.3.8), codetools(v.0.2-20),
lattice(v.0.22-6), tibble(v.3.2.1),
Biobase(v.2.62.0), withr(v.3.0.0),
evaluate(v.0.23), xml2(v.1.3.6),
mclust(v.6.1.1), survMisc(v.0.5.6),
pillar(v.1.9.0), MatrixGenerics(v.1.14.0),
carData(v.3.0-5), stats4(v.4.3.3),
generics(v.0.1.3), RCurl(v.1.98-1.14),
S4Vectors(v.0.40.2), commonmark(v.1.9.1),
munsell(v.0.5.1), scales(v.1.3.0),
xtable(v.1.8-4), glue(v.1.7.0),
tools(v.4.3.3), data.table(v.1.15.4),
ggsignif(v.0.6.4), forcats(v.1.0.0),
cowplot(v.1.1.3), grid(v.4.3.3),
colorspace(v.2.1-0), GenomeInfoDbData(v.1.2.11),
beeswarm(v.0.4.0), vipor(v.0.4.7),
cli(v.3.6.2), km.ci(v.0.5-6), fansi(v.1.0.6),
S4Arrays(v.1.2.1), broom.helpers(v.1.15.0),
gt(v.0.10.1), gtable(v.0.3.5),
rstatix(v.0.7.2), sass(v.0.4.9),
digest(v.0.6.35), BiocGenerics(v.0.48.1),
SparseArray(v.1.2.4), farver(v.2.1.1),
htmlwidgets(v.1.6.4), htmltools(v.0.5.8.1) and
lifecycle(v.1.0.4)
{
# additional details
cat("\n\n")
cat(sprintf("**Source:** *%s in %s project* \n",
knitr::current_input(dir=FALSE), tryCatch({rstudioapi::getActiveProject()},
error = function(e) {NA}) ))
cat(sprintf("**Knit:** *%s by %s* \n", format(Sys.time(), '%Y-%m-%d %H:%M:%S%Z'), Sys.info()[["user"]] ))
cat(tryCatch({sprintf("**Git remote:** *[%s](%s){target='_blank'}* [%s]\n", git2r::remote_url(), git2r::remote_url(), git2r::commits(git2r::repository("."), n=1)[[1]][["sha"]])}, error = function(e) {NULL}))
}
Source: project1.rmd in
/Users/arshi/Documents/GitHub/projects_2024 project
Knit: 2024-05-08 16:07:05EDT by arshi
Git remote: https://github.com/arorarshi/projects_2024.git
[6a7faa58406ef2af030d8ea98d03ae19d43e4341]
LS0tCnRpdGxlOiAic3VydkNsdXN0OiBhIHN1cGVydmlzZWQgY2x1c3RlcmluZyBhcHByb2FjaCBndWlkZWQgYnkgdGltZS1ldmVudCBkYXRhIgphdXRob3I6ICJBcnNoaSBBcm9yYSIKZGF0ZTogImByIGZvcm1hdChTeXMuRGF0ZSgpLCAnJUIgJWQsICVZJylgIgpvdXRwdXQ6IAogICAgaHRtbF9kb2N1bWVudDoKICAgICAgICBmaWdfY2FwdGlvbjogeWVzCiAgICAgICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICAgICAgdG9jOiB0cnVlCiAgICAgICAgdG9jX2Zsb2F0OiB0cnVlCiAgICAgICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICAgICAgY29kZV9kb3dubG9hZDogdHJ1ZQpudW1iZXJzZWN0aW9uczogeWVzCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICAgIGV2YWw9VFJVRSwKICAgIGVjaG8gPSBUUlVFLAogICAgbWVzc2FnZT1UUlVFLAogICAgY2FjaGU9RkFMU0UsIAogICAgd2FybmluZyA9IFRSVUUsIAogICAgZXJyb3IgPSBGQUxTRSwgCiAgICBjb21tZW50ID0gIiMiLCAKICAgIHRpZHkgPSBGQUxTRSwgCiAgICByZXN1bHRzID0gImFzaXMiLCAKICAgIGZpZy53aWR0aCA9IDUsIAogICAgZmlnLmhlaWdodD01KQprbml0cjo6b3B0c19rbml0JHNldCgKICAgIHJvb3QuZGlyID0gIi4iKQpvcHRpb25zKHdhcm49MSwgd2lkdGg9MjAwKQoKI3BhY2thZ2VzCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkocHVycnIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShzdXJ2aXZhbCkKbGlicmFyeShzdXJ2bWluZXIpCgpsaWJyYXJ5KGRvUGFyYWxsZWwpCmxpYnJhcnkoZ2dyaWRnZXMpCmxpYnJhcnkoRFQpCmxpYnJhcnkoZ3RzdW1tYXJ5KQoKbGlicmFyeShzdXJ2Q2x1c3QpCgojIGxpYnJhcnkoZ2l0MnIpCiMgbGlicmFyeShwYW5kZXIpCgojciBzY3JpcHQgd2l0aCBuZWNlc3NhcnkgZnVuY3Rpb25zIApzb3VyY2UoInNjcmlwdHMvc2ltdWxhdGVfc3VydmNsdXN0X2RhdGFfZnVuLlIiKQpzb3VyY2UoInNjcmlwdHMvdXRpbHMuUiIpCgpteV9jb2xfcGFsIDwtIGMoImRhcmtraGFraSIsImRhcmttYWdlbnRhIiwiZmlyZWJyaWNrMiIsImNvcm5mbG93ZXJibHVlIiwiZ3JlZW4iLCJkYXJrYmx1ZSIsImJsYWNrIiwiaG90cGluazIiKQpgYGAKCiMgQmFja2dyb3VuZAoKSW4gdGhpcyByZXBvcnQgd2Ugd2lsbCBiZSBkZWx2aW5nIGludG8gdW5kZXJzdGFuZGluZyAqKnN1cnZDbHVzdCoqIFteMV0gdmlhIHNpbXVsYXRpbmcgdmFyaW91cyBzY2VuYXJpb3MgaW4gdGhlIGZvcm0gb2YgZGF0YXNldHMuIFRoZSBjaG9pY2Ugb2YgdXNpbmcgc2ltdWxhdGVkIGRhdGFzZXRzIG1ha2VzIHRoZSBjb250ZXh0IG9mIGRhdGEgbmV1dHJhbCBhbmQgdGhlIGZpbmRpbmdzIGZyb20gc3VydkNsdXN0IG1vcmUgYWNjZXNzaWJsZS4gCldlIHdpbGwgZ28gb3ZlciBvdXRwdXRzIGZyb20gc3VydkNsdXN0IG9uIHZhcmlvdXMgc2NlbmFyaW9zIGhpZ2hsaWdodGluZyB0aGUgY29tcGxleCBuYXR1cmUgb2YgdGltZS1ldmVudCBkYXRhIGFuZCBwZXJmb3JtYW5jZSBvZiBzdXJ2Q2x1c3QuCgpOb3RlIHRoYXQsIGluIHRoaXMgcmVwb3J0IG15IGdvYWwgaXMgdG8gZ2V0IHlvdSBleGNpdGVkIGFib3V0IHN1cnZDbHVzdCBhbmQgdG8gd2FsayB5b3UgdGhyb3VnaCB0aGUgcmlnb3JvdXMgdGVzdGluZyBwZXJmb3JtZWQgdmlhIHNpbXVsYXRpb25zIGFuZCBkZXNpZ24gb2Ygc2ltdWxhdGlvbnMgdG8gaXJvbiBvdXQgdGhlIGRldGFpbHMgb2YgdGhlIG1ldGhvZG9sb2d5LiAgCgpGb3IgYSBkZWVwZXIgZGl2ZSwgcGxlYXNlIHJlZmVyIHRvIHRoZSBbcHVibGlzaGVkIG1hbnVzY3JpcHQgd2l0aCAqR2Vub21lIE1lZGljaW5lKl0oIDEwLjExODYvczEzMDczLTAyMC0wMDgwNC04KS4gc3VydkNsdXN0IGNhbiBiZSBkb3dubG9hZGVkIGZyb20gbXkgW0dpdEh1YiByZXBvc2l0b3J5XShodHRwczovL2dpdGh1Yi5jb20vYXJvcmFyc2hpL3N1cnZDbHVzdCkKCmBgYHtyLCBlY2hvPUZBTFNFLCBvdXQud2lkdGg9JzQwJScsIG91dC5leHRyYT0nc3R5bGU9ImZsb2F0OnJpZ2h0OyBwYWRkaW5nOjEwcHgiJ30Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImltYWdlcy9pbnRyb19zdXJ2Y2x1c3QucG5nIikKYGBgCgojIyBXaGF0IGlzIHN1cnZDbHVzdD8gCgpVbnRpbCBub3csIHN1Yi10eXBpbmcgaW4gY2FuY2VyIGJpb2xvZ3kgaGFzIHJlbGllZCBoZWF2aWx5IHVwb24gY2x1c3RlcmluZy9taW5pbmcgb2YgbW9sZWN1bGFyIGRhdGEgYWxvbmUsIG9yIHVuc3VwZXJ2aXNlZCBjbHVzdGVyaW5nIG9mIGJpb2xvZ2ljYWwgc2VxdWVuY2luZyBkYXRhIHN1Y2ggYXMgZ2VuZSBleHByZXNzaW9uIGRhdGEsIEROQSBjb3B5IG51bWJlciBkYXRhLCBnZW5lIG11dGF0aW9uIHN0YXR1cyBldGMgW14yXS4gV2hpbGUgdGhpcyBhcHByb2FjaCBoYXMgYmVlbiBhIGNvcm5lcnN0b25lIGluIHVuZGVyc3RhbmRpbmcgY2FuY2VyIGRpc2Vhc2UgYmlvbG9neSwgd2hlbiB0aGUgdWx0aW1hdGUgZ29hbCBpcyB0byB1bmRlcnN0YW5kIHBhdGllbnQgc3Vydml2YWwsIGEgbW9yZSBmb2N1c2VkIGFwcHJvYWNoIHdpdGggc3Vydml2YWwgb3V0Y29tZSBpcyBkZXNpcmVkLiAgIAoKc3VydkNsdXN0IGlzIGFuIG91dGNvbWUgd2VpZ2h0ZWQgaW50ZWdyYXRpdmUgc3VwZXJ2aXNlZCBjbHVzdGVyaW5nIGFsZ29yaXRobSwgZGVzaWduZWQgdG8gY2xhc3NpZnkgcGF0aWVudHMgYWNjb3JkaW5nIHRvIHRoZWlyIG1vbGVjdWxhciBhcyB3ZWxsIGFzIHRpbWUtZXZlbnQgZGF0YSBvciBlbmQgcG9pbnQgb2YgaW50ZXJlc3Qgc3VjaCBhcyBPdmVyYWxsIFN1cnZpdmFsIChPUyksIFByb2dyZXNzaW9uIEZyZWUgU3Vydml2YWwgKFBGUykgZXRjLiAKCkJlbG93LCB3ZSBkZXNjcmliZSB2YXJpb3VzIHNjZW5hcmlvcyB3aGVyZSBhIHN1cGVydmlzZWQgY2x1c3RlcmluZyBhcHByb2FjaCBzdWNoIGFzIHN1cnZDbHVzdCB3aWxsIGJlIGRlc2lyZWQgdGhhbiBtb3JlIHRyYWRpdGlvbmFsIHVuc3VwZXJ2aXNlZCBjbHVzdGVyaW5nIGFwcHJvYWNoIHN1Y2ggYXMgYGttZWFuc2AuIAoKQWxzbywgaGVyZSAgaXMgYSBxdWljayBbcHJpbWVyXShodHRwczovL3d3dy5lbWlseXphYm9yLmNvbS90dXRvcmlhbHMvc3Vydml2YWxfYW5hbHlzaXNfaW5fcl90dXRvcmlhbC5odG1sKSBieSBteSBjb2xsZWFndWUgb24gc3Vydml2YWwgZGF0YSBhbmFseXNpcy4gIAoKIyMgQXBwbGljYXRpb25zIG9mIHN1cnZDbHVzdAoKQXBwbGljYXRpb25zIG9mIHN1cnZDbHVzdCBpbiBjYW5jZXIgZ2Vub21pY3MgaGF2ZSBiZWVuIGVzdGFibGlzaGVkIGluIHByZXZpb3VzIHdvcmsuIFNlZSB0aGUgbGlzdCBvZiBzY2llbnRpZmljIGxpdGVyYXR1cmUgY2l0aW5nIFsqKnN1cnZDbHVzdCoqXShodHRwczovL3NjaG9sYXIuZ29vZ2xlLmNvbS9zY2hvbGFyP29pPWJpYnMmaGw9ZW4mY2l0ZXM9NTgxOTk4MDc5MzcwNjIxNzYwMCkKCkEgcG9zc2libGUgZXh0ZW5zaW9uIGNvdWxkIGJlIHNheSwgd2Ugb2JzZXJ2ZSB0aGUgbGlmZSBvZiBBcHBsZSBNYWNCb29rIFByb3MgcHVyY2hhc2VkIGluIHRoZSB5ZWFyIDIwMTIgZnJvbSB5ZWFycyAyMDEzLTIwMTguIApXZSBkZWZpbmUgYW4gZXZlbnQgYXMgYSBsYXB0b3AgZmFpbGluZyBlbnRpcmVseSAoY29kZWQgYXMgMSkgd2hlcmUgaXQgY291bGRuJ3QgYmUgcmV2aXZlZCBieSBhbiBBcHBsZSBnZW5pdXMuIElmIHRoZSBNYWNCb29rIGlzIHN0aWxsIGZ1bmN0aW9uaW5nIGFmdGVyIDIwMTgsIHdlIGNvbnNpZGVyIGl0IGFzIGEgbm9uIGV2ZW50LCAoY29kZWQgYXMgMCkuIElmIGEgTWFjQm9vb2sgUHJvIGRyb3BzIGZyb20gYSBzdHVkeSwgb3Igd2UgYXJlIHVuYWJsZSB0byBrZWVwIHRyYWNrIG9mIHRoZWlyIHN0YXR1cyBha2EgbG9zcyBvZiBmb2xsb3cgdXAsIHdlIHdpbGwgY2xhc3NpZnkgdGhpcyBldmVudCBhcyBjZW5zb3JlZC4gCgpBcGFydCBmcm9tIHRoaXMgd2UgYWxzbyBjb2xsZWN0IHZhcmlvdXMgZmVhdHVyZXMgYWZ0ZXIgMXlyIG9mIHVzYWdlIC0gYmF0dGVyeSBoZWFsdGgsIHByb2Nlc3NvciBjaGlwIGRldGFpbHMsIG51bWJlciBvZiBhbGl2ZSBwaXhlbHMgb24gdGhlIHNjcmVlbiwgdGVzdHMgdGhhdCBjb3VsZCBtZWFzdXJlIHBlcmZvcm1hbmNlIGV0Yy4gCgpXZSBhcmUgaW50ZXJlc3RlZCBpbiBzZWVpbmcgaWYgd2UgY2FuIGlkZW50aWZ5IGJhdGNoZXMgb2YgTWFjQm9vayBQcm9zIHRoYXQgaGF2ZSB2YXJpZWQgbG9uZ2V2aXR5IChnb29kIG9yIGJhZCkgYmFzZWQgb24gdGhlIGZlYXR1cmVzIHRoYXQgd2UgY29sbGVjdGVkLiAKClRoaXMgaXMgYSB2ZXJ5IGJyb2FkIGV4YW1wbGUgYW5kIGEgcHJvcGVyIHN0dWR5IGRlc2lnbiBtaWdodCByZXF1aXJlIGEgZGVlcCBkaXZlIGludG8gdW5kZXJzdGFuZGluZyBtb3JlIGFib3V0IEFwcGxlIE1hY0Jvb2sgUHJvcy4gRm9yIGV4YW1wbGUsIGRlZmluaXRpb24gb2YgYW4gZXZlbnQgaXMgY3J1Y2lhbCB3aGVuIGFuYWx5emluZyBhIHN0dWR5IHdpdGggdGltZS1ldmVudCBkYXRhLiAKCgojIyBzdXJ2Q2x1c3Qgd29ya2Zsb3cgLSB0aGUgdGxkciB2ZXJzaW9uIHsjd2Zsb3d9CgpzdXJ2Q2x1c3QgaXMgYXZhaWxhYmxlIGFzIGFuIFIgcGFja2FnZSBhbmQgaXMgdW5kZXIgdGhlIHN1Ym1pc3Npb24gcHJvY2VzcyB3aXRoIFtCaW9jb25kdWN0b3JdKGh0dHBzOi8vd3d3LmJpb2NvbmR1Y3Rvci5vcmcpLiBCZWxvdyB3ZSBkZXNjcmliZSB0aGUgcHJvcG9zZWQgd29ya2Zsb3cgb2Ygc3VydkNsdXN0IG1ldGhvZDoKCmBgYHtyLCBlY2hvPUZBTFNFfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiaW1hZ2VzL3N1cnZDbHVzdF93b3JrZmxvdy5wbmciKQpgYGAKCmBnZXREaXN0YCAtIENvbXB1dGUgYSB3ZWlnaHRlZCBkaXN0YW5jZSBtYXRyaXggYmFzZWQgb24gb3V0Y29tZSBhY3Jvc3MgZ2l2ZW4gYG1gIGRhdGEgdHlwZXMuIFN0YW5kYXJkaXphdGlvbiBhY3Jvc3MgZGF0YSB0eXBlcyAoYmluYXJ5LCBjb250aW51b3VzKSB0byBmYWNpbGl0YXRlIHRoZSBpbnRlZ3JhdGlvbiBwcm9jZXNzIGFuZCBhY2NvdW50aW5nIGZvciBub24tb3ZlcmxhcHBpbmcgc2FtcGxlcyBpcyBhbHNvIGFjY29tcGxpc2hlZCBpbiB0aGlzIHN0ZXAuCgpgY29tYmluZURpc3RgIC0gSW50ZWdyYXRlIGBtYCBkYXRhIHR5cGVzIGJ5IGF2ZXJhZ2luZyBvdmVyIGBtYCB3ZWlnaHRlZCBkaXN0YW5jZSBtYXRyaWNlcy4KCmBzdXJ2Q2x1c3RgIGFuZCBgY3Zfc3VydmNsdXN0YCAtIENsdXN0ZXIgaW50ZWdyYXRlZCB3ZWlnaHRlZCBkaXN0YW5jZSBtYXRyaWNlcyB2aWEgYHN1cnZDbHVzdGAuIE9wdGltYWwgYGtgIGlzIGVzdGltYXRlZCB2aWEgY3Jvc3MtdmFsaWRhdGlvbiB1c2luZyBgY3Zfc3VydmNsdXN0YC4gQ3Jvc3MtdmFsaWRhdGVkIHJlc3VsdHMgYXJlIGFzc2Vzc2VkIG92ZXIgdGhlIGZvbGxvd2luZyBwZXJmb3JtYW5jZSBtZXRyaWNzIC0gdGhlICoqbG9ncmFuayBzdGF0aXN0aWMsIHN0YW5kYXJkaXplZCBwb29sZWQgd2l0aGluLWNsdXN0ZXIgc3VtIG9mIHNxdWFyZXMgKFNQV1NTKSoqIGFuZCBzbWFsbCBjbHVzdGVyIHNvbHV0aW9ucyB3aXRoICoqc2l6ZSBsZXNzIHRoYW4gNSBzYW1wbGVzKiouCgoqKipOb3RlOiAqKioKCigxKSBUaGUgaW5wdXQgZGF0YXR5cGVzIG5lZWRzIHRvIGJlIGNhcmVmdWxseSBwcmUtcHJvY2Vzc2VkLiBTZWUgbW9yZSBkZXRhaWxzIHVuZGVyIHRoZSBzdXBwbGVtZW50YXJ5IHNlY3Rpb24gb2YgdGhlIG1hbnVzY3JpcHQuICAKCigyKSBgY3Zfc3VydmNsdXN0YCBpcyBhIHdyYXBwZXIgZnVuY3Rpb24gdGhhdCBjcm9zcy12YWxpZGF0ZXMgYW5kIG91dHB1dHMgY2x1c3RlciBhc3NpZ25tZW50cy4gSWYgeW91IHJ1biB3aXRob3V0IGNyb3NzIHZhbGlkYXRpb24gYW5kIGp1c3QgdGhlIGNvbW1hbmRzIG9uIGl0cyBvd24gKGBnZXREaXN0YCwgYGNvbWJpbmVEaXN0YCBhbmQgYHN1cnZDbHVzdGApLCB5b3UgYXJlICoqb3Zlci1maXR0aW5nISoqLiBSdW5uaW5nIG11bHRpcGxlIChhdCBsZWFzdCAxMCkgcm91bmRzIG9mIGNyb3NzIHZhbGlkYXRpb24gaXMgc3Ryb25nbHkgcmVjb21tZW5kZWQgdG8gYXJyaXZlIGF0IGEgc3RhYmxlIHNvbHV0aW9uLiBBbHNvLCB0aGUgY3Jvc3MtdmFsaWRhdGlvbiBzdGVwIGlzIGNvbXB1dGF0aW9uYWxseSBpbnRlbnNpdmUgYW5kIG9uZSBjYW4gcmVkdWNlIHJ1biB0aW1lcyBieSB1c2luZyBwYXJhbGxlbCBjb21wdXRpbmcuIAoKPGRldGFpbHM+Cgo8c3VtbWFyeT5SZWFkIG1vcmUgYWJvdXQgdGhlIHBlcmZvcm1hbmNlIG1ldHJpY3MgaGVyZSBhbmQgaG93IHRvIGNob3NlIGFuIG9wdGltYWwgYGtgPC9zdW1tYXJ5PgoKKipMb2dyYW5rIHRlc3Qgc3RhdGlzdGljKioKClRoZSBsb2dyYW5rIHRlc3Qgc3RhdGlzdGljW14zXSBpcyBiYXNlZCBvbiBhIG5vbi1wYXJhbWV0cmljIGFwcHJvYWNoIHRoYXQgcXVhbnRpZmllcyBzdXJ2aXZhbCBkaWZmZXJlbmNlIGJldHdlZW4gcmVzdWx0aW5nIHN1YnR5cGVzIGFuZCBtYWtlcyBubyBhc3N1bXB0aW9uIGFib3V0IHRoZSBzdXJ2aXZhbCBkaXN0cmlidXRpb25zLkl0IHRlc3RzIHRoZSBudWxsIGh5cG90aGVzaXMgdGhhdCB0aGVyZSBpcyBubyBkaWZmZXJlbmNlIGluIHN1cnZpdmFsIGJldHdlZW4gdGhlIGdyb3Vwcy4KClRoZSBvcHRpbWFsIGsgaXMgdGhlIG9uZSB3aXRoIHRoZSBtYXhpbXVtIGxvZ3Jhbmsgc3RhdGlzdGljIG9yIGF0IGFuIGluZmxlY3Rpb24gcG9pbnQgd2hlcmUgdGhlIHN0YXRpc3RpYyBwbGF0ZWF1cy4gCgoqKlNQV1NTKioKClNQV1NTIGlzIGNhbGN1bGF0ZWQgYXMgdGhlIHBvb2xlZCB3aXRoaW4tY2x1c3RlciBzdW0gb2Ygc3F1YXJlcyBhbmQgc3RhbmRhcmRpemVkIGJ5IHRoZSB0b3RhbCBzdW0gb2Ygc3F1YXJlcyBzaW1pbGFyIHRvIG1ldGhvZG9sb2d5IHVzZWQgaW4gdGhlIGdhcCBzdGF0aXN0aWMgW140XSB0byBzZWxlY3QgdGhlIGFwcHJvcHJpYXRlIG51bWJlciBvZiBjbHVzdGVycy4KClRoZSBvcHRpbWFsIG51bWJlciBvZiBjbHVzdGVycyBpcyB3aGVyZSBTUFdTUyBpcyBtaW5pbWl6ZWQgYW5kIGNyZWF0ZXMgYW4g4oCcZWxib3figJ0gb3IgYSBwb2ludCBvZiBpbmZsZWN0aW9uLCB3aGVyZSBhZGRpdGlvbiBvZiBtb3JlIGNsdXN0ZXJzIGRvZXMgbm90IGltcHJvdmUgY2x1c3RlciBzZXBhcmF0aW9uLiBTaW5jZSwgdGhpcyBtZXRyaWMgaXMgc3RhbmRhcmRpemVkIChsaWVzIGJldHdlZW4gMC0xKSwgaXQgY2FuIGFsc28gYmUgdXNlZCB0byBjb21wYXJlIGluZGl2aWR1YWwgcnVucyBvZiBkYXRhc2V0cyB0aGF0IGFyZSBtZWFzdXJlZCBvdmVyIHNhbWUgc2FtcGxlcy4KCioqQ2x1c3RlciBTaXplKioKClRoaXMgbWV0cmljIGNvbXB1dGVzIHRoZSBjbHVzdGVyIHNpemUgZm9yIGVhY2ggYGtgIGFjcm9zcyByb3VuZHMgb2YgY3Jvc3MtdmFsaWRhdGlvbi4gSXQgcmVjb3JkcyB0aGUgaW5zdGFuY2VzIHdoZXJlIGEgcm91bmQgb2YgY3Jvc3MgdmFsaWRhdGlvbiByZXR1cm5lZCBhIGNsdXN0ZXIgc2l6ZSB3aXRoIGxlc3MgdGhhbiA1IHN1YmplY3RzLiBTdWNoIGEgc29sdXRpb24gbWlnaHQgYmUgIHVucmVsaWFibGUgYW5kIHBlcmhhcHMgb3Zlci1maXR0ZWQgZm9yIHRoYXQgYGtgLiBBbiBvcHRpbWFsIGBrYCBzaG91bGQgaGF2ZSB0aGUgbGVhc3QgYW1vdW50IG9mIHN1Y2ggaW5zdGFuY2VzIGFjcm9zcyB0aGUgdG90YWwgY3Jvc3MgdmFsaWRhdGlvbiByb3VuZHMuICAKCjwvZGV0YWlscz4KCiMgRGF0YSBzY2VuYXJpb3MKQmVsb3cgd2Ugc2ltdWxhdGUgMyBkYXRhIHNjZW5hcmlvcyB0byBoaWdobGlnaHQgdGhlIGZ1bmN0aW9uYWxpdHkgb2Ygc3VydkNsdXN0LiBTdXJ2aXZhbCB0aW1lcyBmb3IgZGVtb25zdHJhdGlvbiBwdXJwb3NlcyB3aWxsIGFsc28gYmUgc2ltdWxhdGVkIHdoZXJlIHdlIGFzc3VtZSB0aGV5IGFyZSBkZXJpdmVkIGZyb20gYW4gZXhwb25lbnRpYWwgZGlzdHJpYnV0aW9uLiBBbHNvLCBub3RlIHRoYXQgaW4gYWRkaXRpb24gdG8gc2ltdWxhdGVkIGRhdGFzZXQgYmVpbmcgY29udGV4dCBmcmVlLCB3ZSBjYW4gYWxzbyBwcmVkZXRlcm1pbmUgYSBncm91bmQgdHJ1dGggdGhhdCBjb3VsZCBnaXZlIHVzIGEgZmFpciBlc3RpbWF0ZSBvZiBvdXIgbWV0aG9kJ3MgcGVyZm9ybWFuY2UuIAoKQSBoYW5keSBmdW5jdGlvbiB0byBzaW11bGF0ZSBpbnB1dCBkYXRhIGFuZCBzdXJ2aXZhbCBkYXRhIGlzIHByb3ZpZGVkIC0gYHNpbXVsYXRlX3N1cnZjbHVzdF9kYXRhX2Z1bi5SYC4gCgojIyBTY2VuYXJpbyAxCgpIZXJlIHdlIHdpbGwgc2ltdWxhdGUgYSBkYXRhc2V0IHdpdGggdGhyZWUgY2x1c3RlcnMsIGEgdG90YWwgb2YgMTUwICgkY18xPTUwLCBjXzI9NTAsIGNfMz01MCQpIHNhbXBsZXMgYW5kIDE1MCBmZWF0dXJlcyBzdWNoIHRoYXQ6CgokY18xIFxzaW0gTihcbXUgPSAtMS41LFxzaWdtYT0xKSQKCiRjXzIgXHNpbSBOKFxtdSA9IDAsIFxzaWdtYT0xKSQgYW5kCiAgICAgICAgCiRjXzMgXHNpbSBOKFxtdSA9IDEuNSwgXHNpZ21hPTEpJAoKTm90ZSB0aGF0LCBub3QgYWxsIDE1MCBmZWF0dXJlcyB3aWxsIGJlIGluZm9ybWF0aXZlIGFuZCB3ZSB3aWxsIG9ubHkgc2ltdWxhdGUgYWJvdXQgMTUgKDEwJSkgZmVhdHVyZXMgdGhhdCB3aWxsIHJlYWxseSBkcml2ZSB0aGUgZGlzdGluY3Rpb24gYmV0d2VlbiBjbHVzdGVycy4gVGhpcyBpcyBlc3BlY2lhbGx5IHRydWUgaW4gdGhlIGNvbnRleHQgb2YgaHVtYW4gZ2Vub21lIHdpdGggYWJvdXQgfjE5LDAwMCBnZW5lcywgd2hlcmUgb25seSBhIGZldyBvZiB0aGUgZ2VuZXMgd2lsbCBiZSBhY3R1YWxseSBhc3NvY2lhdGVkIHdpdGggdGhlIGRpc2Vhc2UgYmlvbG9neSBhbmQgc3Vydml2YWwuIAoKVGhlc2UgY2x1c3RlcnMgd2lsbCBhbHNvIGhhdmUgZGlzdGluY3QgbWVkaWFuIHN1cnZpdmFsIG9mICA0LjUsIDMuMjUsIGFuZCAyIHlycyByZXNwZWN0aXZlbHkuCgpNb3JlIHJlYWxpc3RpY2FsbHksIGZyb20gdGhlIGZlYXR1cmVzIHRoYXQgYXJlIGRpc3RpbmN0IHNvbWUgYXJlIGdvaW5nIHRvIGJlIGFzc29jaWF0ZWQgd2l0aCBzdXJ2aXZhbCBhbmQgd2hpbGUgb3RoZXJzIHJlbWFpbiBkaXN0aW5jdCB3aXRoIG5vIGFzc29jaWF0aW9uIHRvIHN1cnZpdmFsLiBXZSB3aWxsIHNpbXVsYXRlIGhhbGYgb2YgdGhlIGluZm9ybWF0aXZlIGZlYXR1cmVzIGFzIGRpc3RpbmN0IGFuZCBhc3NvY2lhdGVkIHdpdGggc3Vydml2YWwgYW5kIHJlbWFpbmluZyBoYWxmIGFzIGp1c3QgZGlzdGluY3QuIAoKYGBge3IsIGVjaG89RkFMU0UsIGZpZy5jYXA9ImZpZzpzY2hlbWF0aWNzIG9mIHNjZW5hcmlvMSJ9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJpbWFnZXMvc2NlbmFyaW8xLnBuZyIpCmBgYAoKCmBgYHtyfQojc2ltdWxhdGluZyBzY2VuYXJpbyAxCm1hdF9zY2VuYXJpbzEgPC0gbGlzdCgpCgojZml4aW5nIHRoZSBzb2x1dGlvbiB0byBzaW11bGF0ZWQgc2NlbmFyaW8xCnRydWVfc29sbl9zY2VuYXJpbzEgPC0gZGF0YS5mcmFtZShzYW1wbGVzID0gcGFzdGUwKCJTIiwxOjE1MCkgLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsdXN0ZXIgPSBjKHJlcCgxLDUwKSwgcmVwKDIsNTApLCByZXAoMyw1MCkpKQoKI2Z1bmN0aW9uIGZyb20gc2ltdWxhdGVfc3VydmNsdXN0X2RhdGFfZnVuLlIgdG8gc2ltdWxhdGUgaW5wdXQgZGF0YQptYXRfc2NlbmFyaW8xW1sxXV0gPC0gc2ltdWxhdGVfZGF0YV90eXBlKGs9MywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga19zaXplID0gcmVwKDUwLDMpLCBuX2ZlYXR1cmVzID0gMTUwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG11ID0gYygtMS41LCAwLCAxLjUpLCBzZCA9IHJlcCgxLDMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBlcmNfaW5mb3JtID0gMC4xMCwgbXlfc2VlZCA9IDExMiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwZXJtdXRlID0gVFJVRSkKI3NpbXVsYXRpbmcgc3Vydml2YWwgZGF0YSAKc3Vydl9zY2VuYXJpbzEgPC0gc2ltdWxhdGVfc3Vydl9kYXQoaz0zLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga19zaXplID0gcmVwKDUwLDMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZWRfc3Vydl90aW1lcyA9IGMoNC41LCAzLjI1LCAyKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4X3N1cnZpdmFsID0gMTAsIG15X3NlZWQgPSAxMTIpCgpgYGAKCkJlbG93IHdlIHBsb3QgdGhlIGRpc3RyaWJ1dGlvbiBvZiBvdXIgc2ltdWxhdGVkIDMtY2xhc3MgY2x1c3RlcnMgYW5kIHRoZSBzdXJ2aXZhbCBjdXJ2ZXMgaW4gYSBLYXBsYW4gTWVpZXIgcGxvdC4gCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KCiNsb25nIGZvcm1hdCB0byB3cmFuZ2xlIGR0YSB0byBwbG90IGdncmlkZ2VzIGFuZCBqb2luIHRoZSB0cnV0aCAKbWF0X3NjZW5hcmlvMV9sb25nIDwtIG1hdF9zY2VuYXJpbzFbWzFdXSAlPiUKICAgIGFzLmRhdGEuZnJhbWUoKSAlPiUKICAgIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKHZhciA9ICJzYW1wbGVzIikgJT4lCiAgICB0aWR5cjo6cGl2b3RfbG9uZ2VyKC4sIGNvbHMgPSAtc2FtcGxlcywgbmFtZXNfdG8gPSAiZmVhdHVyZXMiKSAlPiUKICAgIGxlZnRfam9pbiguLCB0cnVlX3NvbG5fc2NlbmFyaW8xLCBieSA9InNhbXBsZXMiKSAlPiUKICAgIG11dGF0ZShjbHVzdGVyID0gYXMuZmFjdG9yKGNsdXN0ZXIpKQoKI2lucHV0IGRhdGEgcGxvdApwMSA9IGdncGxvdChtYXRfc2NlbmFyaW8xX2xvbmcsIGFlcyh4ID0gdmFsdWUsIHkgPSBjbHVzdGVyKSkgKyBnZW9tX2RlbnNpdHlfcmlkZ2VzKGFlcyhmaWxsID0gY2x1c3RlciksIHNjYWxlID0gMiwgYWxwaGEgPSAwLjYpICsgdGhlbWVfYncoKSArIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IG15X2NvbF9wYWxbMTozXSkgKyBnZ3RpdGxlKCJjbHVzdGVyIGRpc3RyaWJ1dGlvbiBvZiBrPTMgY2x1c3RlcnMsIHNjZW5hcmlvMSIpCgojS00gcGxvdApwMiA9IHN1cnZfc2NlbmFyaW8xICU+JQogICAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4odmFyID0gInNhbXBsZXMiKSAlPiUKICAgIGxlZnRfam9pbiguLCB0cnVlX3NvbG5fc2NlbmFyaW8xLCBieSA9InNhbXBsZXMiKSAlPiUKICAgIGdnc3VydnBsb3Qoc3VydmZpdChTdXJ2KHRpbWUsIGV2ZW50KSB+IGNsdXN0ZXIsIGRhdGEgPSAuKSwgZGF0YSA9IC4sIAogICAgICAgICAgICAgICBzdXJ2Lm1lZGlhbi5saW5lID0gInYiLCBwYWxldHRlID0gbXlfY29sX3BhbCkgKyAKICAgIGdndGl0bGUoInN1cnZpdmFsIGRpc3RyaWJ1dGlvbiBhY3Jvc3MgXG4gaz0zIGNsdXN0ZXJzLCBzY2VuYXJpbzEiKQoKCmBgYAoKYGBge3IsIGZpZy53aWR0aD0xMH0KZ2dhcnJhbmdlKHAxLCAocDIkcGxvdCArIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEzKSkpLCBucm93PTEpCgpgYGAKCldlIHNlZSB0aGF0IHNpbmNlIG9ubHkgMTAlIG9mIHRoZSBmZWF0dXJlcyBhcmUgaW5mb3JtYXRpdmUgYW5kIDkwJSBhcmUgbm9pc2UsIHRoZSBvdmVyYWxsIGRpc3RyaWJ1dGlvbiBhcHBlYXJzIHRvIGJlIHVuaWZvcm0gZm9yIHRoZSB0aHJlZSBjbHVzdGVycy4gCgpUaGUgZGFzaGVkIHZlcnRpY2FsIGxpbmUgZnJvbSBlYWNoIHN1cnZpdmFsIGN1cnZlIHJlcHJlc2VudHMgdGhlIG1lZGlhbiBzdXJ2aXZhbCByYXRlIGZvciBlYWNoIGdyb3VwLiBTZWUgYmVsb3cgdGhlIGFjdHVhbCBmaXQgZnJvbSBgc3VydmZpdGAsIGFzIGNvbXBhcmVkIHdoYXQgd2UgYWN0dWFsbHkgdXNlZCBpbiBvdXQgc2ltdWxhdGlvbiwgJFxsYW1iZGFfe2MxfSA9IDQuNSwgXGxhbWJkYV97YzJ9PTMuMjUsIFxsYW1iZGFfe2MzfSA9IDIkIAoKYGBge3J9CiNzdXJ2Zml0IHRvIGNvbXB1dGUgbWVkaWFuIHN1cnZpdmFsIHRpbWVzCmZpdCA8LSBzdXJ2X3NjZW5hcmlvMSAlPiUKICAgIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKHZhciA9ICJzYW1wbGVzIikgJT4lCiAgICBsZWZ0X2pvaW4oLiwgdHJ1ZV9zb2xuX3NjZW5hcmlvMSwgYnkgPSJzYW1wbGVzIikgJT4lCiAgICBzdXJ2Zml0KFN1cnYodGltZSwgZXZlbnQpIH4gY2x1c3RlciwgZGF0YSA9IC4pCgpmaXRfbWVkaWFuIDwtIHN1bW1hcnkoZml0KSR0YWJsZVssJ21lZGlhbiddIAoKZGF0YS5mcmFtZShjbHVzdGVyID0gZ3N1YigiY2x1c3Rlcj0iLCJjIixuYW1lcyhmaXRfbWVkaWFuKSksIAogICAgICAgICAgIGBtZWRpYW4gc3Vydml2YWwoeXJzKWAgPSByb3VuZChmaXRfbWVkaWFuLDIpKSAlPiUgCiAgZ3Q6Omd0KGNhcHRpb24gPSAibWVkaWFuIHN1cnZpdmFsIHRpbWVzIGFyZSBmcm9tIGBzdXJ2Zml0YCBmaXQiKQoKYGBgCgoKIyMjIHN1cnZDbHVzdCBydW4gCgpPbmNlIHRoZSBzaW11bGF0ZWQgZGF0YSBpcyBhc3NlbWJsZWQsIHdlIHByb2NlZWQgdG8gcnVubmluZyBzdXJ2Q2x1c3QgdmlhIGBjdl9jdXJ2Y2x1c3RgLiBFYWNoIGBrYCB3aWxsIGJlIGNyb3NzLXZhbGlkYXRlZCBhY3Jvc3MgMy1mb2xkcyBmb3IgMTAgcm91bmRzIHdoZXJlIGZyZXNoIHJlc2FtcGxpbmcgZm9yIGZvbGRzIGlzIGRvbmUgYXQgZXZlcnkgcm91bmQuIENvbXB1dGF0aW9uIGFjcm9zcyBlYWNoIGBrYCAod2hlcmUgayA9IDIgdG8gNyBjbHVzdGVycykgd2lsbCBiZSBwYXJhbGxlbGl6ZWQgdXNpbmcgYGRvcGFyYCBmcm9tIHBhY2thZ2UgYGRvUGFyYWxsZWxgLiAKClRoaXMgaXMgd3JhcHBlZCB1cCBpbiBhIGZ1bmN0aW9uIGJ5IHRoZSBuYW1lIG9mIGBzaW1fY3Zyb3VuZHNgLCBFeHBhbmQgdGhlIGNvZGUgc2VjdGlvbiBiZWxvdy4gCgpgYGB7ciwgZWNobz1UUlVFLCBjb2xsYXBzZT1GQUxTRSwgcmVzdWx0cz0nbWFya3VwJ30Kc2ltX2N2cm91bmRzPC1mdW5jdGlvbihraywgc2ltZGF0LCBzaW1zdXJ2ZGF0LCBmb2xkPTMsIGN2X3JvdW5kcz0xMCl7CiAgICB0aGlzLmZvbGQ8LWZvbGQKICAgIGZpdDwtbGlzdCgpCiAgICBmb3IgKGkgaW4gc2VxX2xlbihjdl9yb3VuZHMpKXsKICAgICAgICBmaXRbW2ldXSA8LSBzdXJ2Q2x1c3Q6OmN2X3N1cnZjbHVzdChzaW1kYXQsIHNpbXN1cnZkYXQsa2ssdGhpcy5mb2xkKQogICAgfQogICAgcmV0dXJuKGZpdCkKfSAKYGBgCgpgYGB7cn0KcmVnaXN0ZXJEb1BhcmFsbGVsKGNsIDwtIG1ha2VDbHVzdGVyKDYpKQoKcHRtIDwtIFN5cy50aW1lKCkKCiNlYWNoIGNvcmUgcGVyZm9ybXMgY29tcHV0YXRpb24gZm9yIGVhY2ggayBjbHVzdGVyCnNpbTFfY3ZfZml0IDwtIGZvcmVhY2goa2s9Mjo3KSAlZG9wYXIlIHNpbV9jdnJvdW5kcyhraywgc2ltZGF0ID0gbWF0X3NjZW5hcmlvMSwgc2ltc3VydmRhdCA9IHN1cnZfc2NlbmFyaW8xKQoKcHRtMiA8LSBTeXMudGltZSgpCnN0b3BDbHVzdGVyKGNsKQoKdHQgPSBwdG0yLXB0bQpgYGAKClRoZSBjcm9zcyB2YWxpZGF0aW9uIHRvb2sgYHIgcm91bmQoYXMuZG91YmxlKHR0LCB1bml0cyA9ICJtaW5zIiksMilgIG1pbnV0ZXMgdG8gcnVuLiAKCiMjIyBSZXN1bHRzIGFuZCBjaG9vc2luZyBrCgpBbGwgY3Jvc3MgdmFsaWRhdGVkIHJvdW5kcyBvZiBkZXNpcmVkIGsgY2x1c3RlcnMgaXMgdGhlbiBldmFsdWF0ZWQgYWNyb3NzIDMgbWV0cmljcyAtIAoKICogTG9ncmFuayB0ZXN0IHN0YXRpc3RpYwogKiBTdGFuZGFyZGl6ZWQgcG9vbGVkIHdpdGhpbiBzdW0gb2Ygc3F1YXJlcyAoU1BXU1MpCiAqIE51bWJlciBvZiByZWxpYWJsZSBzb2x1dGlvbnMgCgpNb3JlIGRldGFpbHMgb24gdGhlIG1ldHJpY3MgY2FuIGJlIGZvdW5kIFtoZXJlXSgjd2Zsb3cpICAKCkluIHRoZSBwbG90IGJlbG93LCB0aGUgdG9wLWxlZnQgcGxvdCBpcyBzdW1tYXJpemluZyAqKmxvZ3JhbmsgdGVzdCBzdGF0aXN0aWMqKiBvdmVyIDEwIHJvdW5kcyBvZiBjcm9zcy12YWxpZGF0ZWQgY2xhc3MgbGFiZWxzIGFjcm9zcyAzLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbi4gRWFjaCBkb3QgaXMgYSBzb2x1dGlvbiBmcm9tIGEgY3Jvc3MgdmFsaWRhdGVkIHJvdW5kIGZvciB0aGF0IGBrYC4gSGVyZSwgd2Ugc2VlIHRoYXQgbG9ncmFuayBwZWFrcyBhdCBgaz0zYC4gCgpUaGUgdG9wLXJpZ2h0IHBsb3QgaXMgc3VtbWFyaXppbmcgU1BXU1Mgb3IgKipTdGFuZGFyZGl6ZWQgUG9vbGVkIFdpdGhpbiBTdW0gb2YgU3F1YXJlcyoqLiBJdCBhcHBlYXJzIHRoYXQgU1BXU1MgZGVjcmVhc2VzIG1vbm90b25pY2FsbHkgYXMgdGhlIG51bWJlciBvZiBjbHVzdGVycyBga2AgaW5jcmVhc2VzLiBUaGUgb3B0aW1hbCBudW1iZXIgb2YgY2x1c3RlcnMgaXMgd2hlcmUgU1BXU1MgY3JlYXRlcyBhbiDigJxlbGJvd+KAnSAsIGhlcmUgdGhlIHBsb3QgZWxib3dzIGF0IGBrPTNgCgpUaGUgbGFzdCBwbG90LCBvbiB0aGUgYm90dG9tLWxlZnQsIHNob3dzIGZvciBlYWNoIGBrYCBob3cgbWFueSBga2AgY2xhc3MgbGFiZWxzIGhhdmUgYDw9NWAgc2FtcGxlcyBpbiAxMCByb3VuZHMgb2YgY3Jvc3MgdmFsaWRhdGlvbi4gSW4gb3VyIGNhc2UgaGVyZSwgZm9yIGBrPTNgIHRoZSBudW1iZXIgb2YgY2xhc3NlcyB3aXRoIGA8PTVgIHNhbXBsZXMgaXMgemVyby4gCgpgYGB7cn0Kc3Nfc3RhdHMgPC0gZ2V0U3RhdHMoc2ltMV9jdl9maXQsIGtrPTcsIGN2cj0xMCkKCnBfbHIgPC0gcGxvdFN0YXRzX3RpZHkoc3Nfc3RhdHMkbHIsIGhpZ2hsaWdodF9rID0gMykKcF9sciA8LSBwX2xyICsgeWxhYigibG9ncmFuayB0ZXN0IHN0YXRpc3RpYyIpCgpwX3Nwd3NzIDwtIHBsb3RTdGF0c190aWR5KHNzX3N0YXRzJHNwd3NzLCBoaWdobGlnaHRfayA9IDMpCnBfc3B3c3MgPC0gcF9zcHdzcyArIHlsYWIoIlNQV1NTIikKCnBfY2x1c2l6ZSA8LSBkYXRhLmZyYW1lKGsgPSBwYXN0ZTAoImsiLDI6NyksIGN2X2xlc3NfdGhhbl81ID0gc3Nfc3RhdHMkYmFkLnNvbCkgJT4lCiAgICBnZ3Bsb3QoYWVzKHg9aywgeSA9IGN2X2xlc3NfdGhhbl81KSkgKyAKICAgIGdlb21fbGluZShjb2wgPSAic2t5Ymx1ZSIsIGdyb3VwID0gImsiKSArIGdlb21fcG9pbnQoKSArIAogICAgdGhlbWVfbWluaW1hbCgpICsgeWxhYigibm8uIG9mIGN2IHNvbHV0aW9uIHdpdGggPD01IHNhbXBsZXMiKQoKYGBgCgoKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTh9CmdnYXJyYW5nZShwX2xyLCBwX3Nwd3NzLCBwX2NsdXNpemUsIG5yb3c9MiwgbmNvbD0yKQpgYGAKCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KI2N2X3ZvdGluZyBjcmVhdGVzIGEgY29uc2Vuc3VzIG9mIGFsbCBzb2x1dGlvbnMgYWNyb3NzIHRoZSAxMCBjcm9zcyB2YWxpZGF0ZWQgcm91bmRzIAprMyA8LSBjdl92b3RpbmcoY3YuZml0ID0gc2ltMV9jdl9maXQsCiAgICAgICAgICAgICAgICBkYXQuZGlzdCA9IGdldERpc3QoZGF0YXNldHMgPSBtYXRfc2NlbmFyaW8xLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1cnZkYXQgPSBzdXJ2X3NjZW5hcmlvMSksIAogICAgICAgICAgICAgICAgcGlja19rPTMpCmBgYAoKIyMjIyBrPTMKCkZyb20gdGhlIG1ldHJpY3MgYWJvdmUsIHdlIGNob29zZSBgaz0zYCBhcyBvdXIgb3B0aW1hbCBrLiBXZSBjYW4gY29tcGFyZSB0aGlzIHdpdGggb3VyIHNpbXVsYXRlZCB0cnVlIHNvbHV0aW9uIC0gCgpgYGB7cn0KI2NyZWF0aW5nIHRhYiB0byBzdG9yZSByZXN1bHRzIAp0YWIgPC0gdHJ1ZV9zb2xuX3NjZW5hcmlvMSAlPiUgCiAgICBsZWZ0X2pvaW4oLiwgZGF0YS5mcmFtZShzaW1fc29sbiA9IGszLCBzYW1wbGVzID0gcGFzdGUwKCJTIiwgMToxNTApKSwgCiAgICAgICAgICAgICAgYnkgPSAic2FtcGxlcyIpCgp0YWIgJT4lIAogICAgZHBseXI6OnNlbGVjdCgtc2FtcGxlcykgJT4lIAogICAgZHBseXI6OnJlbmFtZSgidHJ1dGgiID0gY2x1c3RlcikgJT4lCiAgICBndHN1bW1hcnk6OnRibF9zdW1tYXJ5KGJ5ID0gc2ltX3NvbG4pICU+JSBtb2RpZnlfaGVhZGVyKGxhYmVsID0gIioqc2ltdWxhdGVkIHNvbHV0aW9uKioiKQoKYXJpIDwtIHJvdW5kKG1jbHVzdDo6YWRqdXN0ZWRSYW5kSW5kZXgodGFiJHNpbV9zb2xuLCB0YWIkY2x1c3RlciksMikKYGBgCgpUaGUgY29uY29yZGFuY2UgYmV0d2VlbiB0cnVlIGFuZCBzdXJ2Q2x1c3QgcHJlZGljdGVkIHNvbHV0aW9uIGNhbiBiZSBjb21wdXRlZCBieSBSYW5kIEluZGV4LiAKClRoZSBSYW5kIEluZGV4IGlzIHVzZWQgdG8gbWVhc3VyZSBhZ3JlZW1lbnQgYmV0d2VlbiB0d28gY2xhc3NpZmljYXRpb24gbGFiZWxzLiBJdCBoYXMgYSBtYXhpbXVtIG9mIDEgYW5kIGEgbWluaW11bSBvZiAwLiBIZXJlIDAgbWVhbnMgdGhlIHR3byBkYXRhIGxhYmVscyBoYXZlIG5vIHNoYXJlZCBpbmZvcm1hdGlvbiBhbmQgMSBtZWFucyB0aGV5IGFyZSB0aGUgc2FtZSBsYWJlbHMuCgojIyMgQ29uY2x1c2lvbiAKClRoZSBjb25jb3JkYW5jZSBiZXR3ZWVuIHRoZSBzdXJ2Q2x1c3QgcmVwb3J0ZWQgc29sdXRpb25zIGFuZCB0cnV0aCBjbGFzcyBsYWJlbHMgaXMgaGlnaCBgciBhcmlgLCBtZWFuaW5nIHN1cnZDbHVzdCBkaWQgYSBnb29kIGpvYiBpbiByZWNvdmVyaW5nIHRoZSB0cnVlIHNvbHV0aW9uIHdoZW4gZmVhdHVyZXMgY2FycnkgYSBtaXhlZCBzaWduYWwuIEFsc28sIG5vdGUgdGhhdCwgZXZlbiB0aG91Z2ggb25seSAxMCUgb2YgdGhlIGZlYXR1cmVzIHdlcmUgaW5mb3JtYXRpdmUsIHdlIHNwbGl0IHRoaXMgaW4gaGFsZiB0byBzaW11bGF0ZSBmZWF0dXJlcyB0aGF0IHdlcmUgZGlzdGluY3QgYW5kIGFzc29jaWF0ZWQgd2l0aCBzdXJ2aXZhbCBhbmQgc3VydkNsdXN0IHdhcyBhYmxlIHRvIGJvcnJvdyBmcm9tIHRoZSA1JSBvZiB0aGUgaW5mb3JtYXRpdmUgZmVhdHVyZXMuIAoKCiMjIFNjZW5hcmlvIDIKCnN1cnZDbHVzdCBjYW4gcGVyZm9ybSBjbHVzdGVyaW5nIG9uIG1vcmUgdGhhbiBvbmUgZGF0YSB0eXBlIGFuZCBjYW4gb3V0cHV0IHJlc3VsdHMgYnkgaW50ZWdyYXRpbmcgdGhlIGRhdGFzZXRzLiBJbiBnZW5vbWljIGNvbnRleHQsIHRoZXNlIGRpZmZlcmVudCBkYXRhdHlwZXMgYXJlIHRoZSBkaWZmZXJlbnQgYmlvbG9naWNhbCBhdmVudWVzIHN1Y2ggYXMgZ2VuZSBleHByZXNzaW9uIGRhdGEgKGNvbnRpbnVvdXMpLCBnZW5lIG11dGF0aW9uIHN0YXR1cyAoYmluYXJ5KSBldGMuIGNhcHR1cmluZyBkaWZmZXJlbnQgbGF5ZXJzIG9mIHRoZSB0dW1vciBiaW9sb2d5LiAKCkhlcmUgd2Ugd2lsbCBzaW11bGF0ZSB0d28gZGF0YXNldCB3aXRoIHRocmVlIGNsdXN0ZXJzLCBhIHRvdGFsIG9mIDE1MCBzYW1wbGVzIGFuZCAxMDAgZmVhdHVyZXMgd2l0aCBvbmUgZGF0YXNldCBoYXZpbmcgc3Ryb25nIGNsdXN0ZXJpbmcgaW5mb3JtYXRpb24gYW5kIHRoZSBvdGhlciBoYXZpbmcgd2VhayBjbHVzdGVyaW5nIGluZm9ybWF0aW9uLiBTZWUgYmVsb3cgLSAKCipEYXRhc2V0IDEqCgpTaW11bGF0ZSB0aHJlZSBjbHVzdGVycyAoJGNfMT01MCwgY18yPTUwLCBjXzM9NTAkKSBzdWNoIHRoYXQKCiRjXzEgXHNpbSBOKFxtdSA9IC0xLjUsXHNpZ21hPTEpJCwKICAgICAgICAKJGNfMiBcc2ltIE4oXG11ID0gMCwgXHNpZ21hPTEpJCBhbmQKCiRjXzMgXHNpbSBOKFxtdSA9IDEuNSwgXHNpZ21hPTEpJAoKKkRhdGFzZXQgMioKClNpbXVsYXRlIHRocmVlIGNsdXN0ZXJzICgkY18xPTUwLCBjXzI9NTAsIGNfMz01MCQpIHN1Y2ggdGhhdCAtIAoKJGNfMSBcc2ltIE4oXG11ID0gLTAuNSxcc2lnbWE9MSkkLAogICAgICAgIAokY18yIFxzaW0gTihcbXUgPSAwLCBcc2lnbWE9MSkkIGFuZAogICAgICAgIAokY18zIFxzaW0gTihcbXUgPSAwLjUsIFxzaWdtYT0xKSQKICAgICAgICAKRGF0YXNldDIgY2FuIGJlIGludGVycHJldGVkIGFzIGEgbW9kYWxpdHkgdGhhdCBpcyBhZGRpbmcgbm9pc2UgdG8gdGhlIG1vZGFsaXR5IG9mIHRoZSBkYXRhIHRoYXQgZXhwbGFpbnMgdGhlIHRydWUgc3Vydml2YWwgYXNzb2NpYXRpb24uIEZvciB0aGlzIHNjZW5hcmlvLCB0aGUgc3Vydml2YWwgZGF0YSByZW1haW5zIHNhbWUgYXMgc2NlbmFyaW8gMS4gCgpgYGB7cn0KCiNzY2VuYXJpbyAyIHNldCB1cAptYXRfc2NlbmFyaW8yIDwtIGxpc3QoKQoKdHJ1ZV9zb2xuX3NjZW5hcmlvMiA8LSB0cnVlX3NvbG5fc2NlbmFyaW8xCgojc2ltdWxhdGUgZGF0YSBzZXQgYnV0IHdpdGggc3Ryb25nIGNsdXN0ZXIgYXNzb2NpYXRpb24gCm1hdF9zY2VuYXJpbzJbWzFdXSA8LSBzaW11bGF0ZV9kYXRhX3R5cGUoaz0zLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrX3NpemUgPSByZXAoNTAsMyksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fZmVhdHVyZXMgPSAxMDAsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG11ID0gYygtMS41LCAwLCAxLjUpLCBzZCA9IHJlcCgxLDMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBlcmNfaW5mb3JtID0gMC4xMCwgbXlfc2VlZCA9IDEyMykKCgojc2ltdWxhdGUgYW4gYWRkaXRpb25hbCBkYXRhIHNldCBidXQgd2l0aCB3ZWFrIGNsdXN0ZXIgYXNzb2NpYXRpb24gCm1hdF9zY2VuYXJpbzJbWzJdXSA8LSBzaW11bGF0ZV9kYXRhX3R5cGUoaz0zLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrX3NpemUgPSByZXAoNTAsMyksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fZmVhdHVyZXMgPSAxNTAsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG11ID0gYygtMC41LCAwLCAwLjUpLCBzZCA9IHJlcCgxLDMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBlcmNfaW5mb3JtID0gMC4xMCwgbXlfc2VlZCA9IDEyMykKCnN1cnZfc2NlbmFyaW8yIDwtIHN1cnZfc2NlbmFyaW8xCgpgYGAKCkJlbG93LCB3ZSBwbG90IHRoZSBkaXN0cmlidXRpb24gb2Ygb25seSBpbmZvcm1hdGl2ZSBmZWF0dXJlcyBhY3Jvc3MgY2x1c3RlcnMgYWNyb3NzIHRoZSB0d28gZGF0YXNldHMuIFRoZSBkYXNoZWQgZ3JleSBsaW5lcyByZXByZXNlbnQgdGhlIG1lYW5zIGFzIHdlIHNpbXVsYXRlZCBhY3Jvc3MgdGhlIHR3byBkYXRhc2V0cy4gCgpgYGB7ciwgZmlnLndpZHRoPTh9CgojbG9uZyBmb3JtIG9mIGRhdGFzZXQgdG8gdmlzdWFsaXplIGNsdXN0ZXIgZGlzdHJpYnV0aW9uIAojZGF0YXNldDEKbWF0X3NjZW5hcmlvMl9kYXQxX2xvbmcgPC0gbWF0X3NjZW5hcmlvMltbMV1dICU+JQogICAgYXMuZGF0YS5mcmFtZSgpICU+JQogICAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4odmFyID0gInNhbXBsZXMiKSAlPiUKICAgIHRpZHlyOjpwaXZvdF9sb25nZXIoLiwgY29scyA9IC1zYW1wbGVzLCBuYW1lc190byA9ICJmZWF0dXJlcyIpICU+JQogICAgbGVmdF9qb2luKC4sIHRydWVfc29sbl9zY2VuYXJpbzIsIGJ5ID0ic2FtcGxlcyIpICU+JQogICAgbXV0YXRlKGNsdXN0ZXIgPSBhcy5mYWN0b3IoY2x1c3RlcikpCgpwMSA8LSBnZ3Bsb3QobWF0X3NjZW5hcmlvMl9kYXQxX2xvbmcgJT4lIAogICAgICAgICAgICAgICAgZmlsdGVyKGZlYXR1cmVzICVpbiUgcGFzdGUwKCJGIiwxOjEwICkpLCBhZXMoeCA9IHZhbHVlLCB5ID0gY2x1c3RlcikpICsgCiAgICBnZW9tX2RlbnNpdHlfcmlkZ2VzKGFlcyhmaWxsID0gY2x1c3RlciksIHNjYWxlID0gMiwgYWxwaGEgPSAwLjYpICsgdGhlbWVfYncoKSArIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IG15X2NvbF9wYWxbMTozXSkgKyAKICAgIGdndGl0bGUoImRpc3RyaWJ1dGlvbiBvZiBpbmZvcm1hdGl2ZSBmZWF0dXJlcyBvbmx5LCBkYXRhc2V0MSwgc2NlbmFyaW8yIikgCgpwMSA8LSBwMSArCiAgICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9MTApKSArIAogICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gYygtMS41LDAsMS41KSwgY29sID0gImdyZXkiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKQoKI2RhdGFzZXQyCm1hdF9zY2VuYXJpbzJfZGF0Ml9sb25nIDwtIG1hdF9zY2VuYXJpbzJbWzJdXSAlPiUKICAgIGFzLmRhdGEuZnJhbWUoKSAlPiUKICAgIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKHZhciA9ICJzYW1wbGVzIikgJT4lCiAgICB0aWR5cjo6cGl2b3RfbG9uZ2VyKC4sIGNvbHMgPSAtc2FtcGxlcywgbmFtZXNfdG8gPSAiZmVhdHVyZXMiKSAlPiUKICAgIGxlZnRfam9pbiguLCB0cnVlX3NvbG5fc2NlbmFyaW8yLCBieSA9InNhbXBsZXMiKSAlPiUKICAgIG11dGF0ZShjbHVzdGVyID0gYXMuZmFjdG9yKGNsdXN0ZXIpKQoKcDIgPC0gZ2dwbG90KG1hdF9zY2VuYXJpbzJfZGF0Ml9sb25nICU+JSAKICAgICAgICAgICAgICAgIGZpbHRlcihmZWF0dXJlcyAlaW4lIHBhc3RlMCgiRiIsMToxMCApKSwgYWVzKHggPSB2YWx1ZSwgeSA9IGNsdXN0ZXIpKSArIAogICAgZ2VvbV9kZW5zaXR5X3JpZGdlcyhhZXMoZmlsbCA9IGNsdXN0ZXIpLCBzY2FsZSA9IDIsIGFscGhhID0gMC42KSArIHRoZW1lX2J3KCkgKyBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBteV9jb2xfcGFsWzE6M10pICsgZ2d0aXRsZSgiZGlzdHJpYnV0aW9uIG9mIGluZm9ybWF0aXZlIGZlYXR1cmVzIG9ubHksIGRhdGFzZXQyLCBzY2VuYXJpbzIiKQoKcDIgPC0gcDIgKyAKICAgIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT0xMCkpICsKICAgIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IGMoLTAuNSwwLDAuNSksIGNvbCA9ICJncmV5IiwgbGluZXR5cGUgPSAiZGFzaGVkIikKCmdnYXJyYW5nZShwMSwgcDIsIG5yb3c9MSkKYGBgCgojIyMgc3VydkNsdXN0IHJ1biAKCkluIHRoaXMgc2VjdGlvbiB3ZSB3aWxsIGJlIHBlcmZvcm1pbmcgd2VpZ2h0ZWQgaW50ZWdyYXRpdmUgY2x1c3RlcmluZyBzdXJ2Q2x1c3QgdmlhIHByZXZpb3VzbHkgZGVmaW5lZCBmdW5jdGlvbiBgc2ltX2N2cm91bmRzYC4KCmBgYHtyfQpyZWdpc3RlckRvUGFyYWxsZWwoY2wgPC0gbWFrZUNsdXN0ZXIoNikpCgpwdG0gPC0gU3lzLnRpbWUoKQpzaW0yX2N2X2ZpdCA8LSBmb3JlYWNoKGtrPTI6NykgJWRvcGFyJSBzaW1fY3Zyb3VuZHMoa2ssIHNpbWRhdCA9IG1hdF9zY2VuYXJpbzIsIHNpbXN1cnZkYXQgPSBzdXJ2X3NjZW5hcmlvMikKcHRtMiA8LSBTeXMudGltZSgpCgpzdG9wQ2x1c3RlcihjbCkKCnR0ID0gcHRtMi1wdG0KYGBgCgpUaGUgY3Jvc3MgdmFsaWRhdGlvbiB0b29rIGByIHJvdW5kKGFzLmRvdWJsZSh0dCwgdW5pdHM9Im1pbnMiKSwyKWAgbWludXRlcyB0byBydW4uIAoKIyMjIFJlc3VsdHMgYW5kIGNob29zaW5nIGsKCkluIHRoZSBwbG90IGJlbG93LCB0aGUgdG9wLWxlZnQgcGxvdCBzdW1tYXJpemluZyBsb2dyYW5rIHRlc3Qgc3RhdGlzdGljIG92ZXIgMTAgcm91bmRzIG9mIGNyb3NzLXZhbGlkYXRlZCBmb3IgZWFjaCBrLCB3ZSBzZWUgaXQgaXMgbWF4aW11bSBmb3IgYGs9M2AuIFRoaXMgaXMgaW4gY29uY29yZGFuY2Ugd2l0aCBTUFdTUywgd2hlcmUgdGhlIHBvaW50IG9mIGluZmxlY3Rpb24gaXMgYWxzbyBhdCBgaz0zYC4gCgpgYGB7cn0KCiNjb2RlIHRvIHNlZSBldmFsdWF0ZSBzdXJ2Q2x1c3QgcnVuIGFjcm9zcyBsb2dyYW5rIHRlc3Qgc3RhdHMsIHNwd3NzCgpzc19zdGF0cyA8LSBnZXRTdGF0cyhzaW0yX2N2X2ZpdCwga2s9NywgY3ZyPTEwKQoKcF9sciA8LSBwbG90U3RhdHNfdGlkeShzc19zdGF0cyRsciwgaGlnaGxpZ2h0X2sgPSAzKQpwX2xyIDwtIHBfbHIgKyB5bGFiKCJsb2dyYW5rIHRlc3Qgc3RhdGlzdGljIikKCnBfc3B3c3MgPC0gcGxvdFN0YXRzX3RpZHkoc3Nfc3RhdHMkc3B3c3MsIGhpZ2hsaWdodF9rID0gMykKcF9zcHdzcyA8LSBwX3Nwd3NzICsgeWxhYigiU1BXU1MiKQoKcF9jbHVzaXplIDwtIGRhdGEuZnJhbWUoayA9IHBhc3RlMCgiayIsMjo3KSwgY3ZfbGVzc190aGFuXzUgPSBzc19zdGF0cyRiYWQuc29sKSAlPiUKICAgIGdncGxvdChhZXMoeD1rLCB5ID0gY3ZfbGVzc190aGFuXzUpKSArIGdlb21fcG9pbnQoKSArIGdlb21fbGluZShjb2wgPSAic2t5Ymx1ZSIsIGdyb3VwPSJrIikgKyB0aGVtZV9taW5pbWFsKCkgKyB5bGFiKCJjdiBzb2x1dGlvbiB3aXRoIDw9NSBzYW1wbGVzIikKYGBgCgoKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTh9CmdnYXJyYW5nZShwX2xyLCBwX3Nwd3NzLCBwX2NsdXNpemUsIG5yb3c9MiwgbmNvbD0yKQpgYGAKCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KazMgPC0gY3Zfdm90aW5nKGN2LmZpdCA9IHNpbTJfY3ZfZml0LAogICAgICAgICAgICAgICAgZGF0LmRpc3QgPSBnZXREaXN0KGRhdGFzZXRzID0gbWF0X3NjZW5hcmlvMiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdXJ2ZGF0ID0gc3Vydl9zY2VuYXJpbzIpLCAKICAgICAgICAgICAgICAgIHBpY2tfaz0zKQpgYGAKCiMjIyMgaz0zCgpGcm9tIHRoZSBtZXRyaWNzIGFib3ZlLCB3ZSBjaG9vc2UgYGs9M2AgYXMgb3VyIG9wdGltYWwgay4gV2UgY2FuIGNvbXBhcmUgdGhpcyB3aXRoIG91ciBzaW11bGF0ZWQgdHJ1ZSBzb2x1dGlvbiAtIAoKYGBge3J9Cgp0YWIgPC0gdHJ1ZV9zb2xuX3NjZW5hcmlvMiAlPiUgCiAgICBsZWZ0X2pvaW4oLiwgZGF0YS5mcmFtZShzaW1fc29sbiA9IGszLCBzYW1wbGVzID0gcGFzdGUwKCJTIiwgMToxNTApKSwgCiAgICAgICAgICAgICAgYnkgPSJzYW1wbGVzIikKCnRhYiAlPiUgCiAgICBkcGx5cjo6c2VsZWN0KC1zYW1wbGVzKSAlPiUgCiAgICBkcGx5cjo6cmVuYW1lKCJ0cnV0aCIgPSBjbHVzdGVyKSAlPiUKICAgIGd0c3VtbWFyeTo6dGJsX3N1bW1hcnkoYnkgPSBzaW1fc29sbikgJT4lIG1vZGlmeV9oZWFkZXIobGFiZWwgPSAiKipzaW11bGF0ZWQgc29sdXRpb24qKiIpCgphcmkgPC0gcm91bmQobWNsdXN0OjphZGp1c3RlZFJhbmRJbmRleCh0YWIkc2ltX3NvbG4sIHRhYiRjbHVzdGVyKSwyKQpgYGAKClRoZSBjb25jb3JkYW5jZSBiZXR3ZWVuIHRydWUgYW5kIHN1cnZDbHVzdCBwcmVkaWN0ZWQgc29sdXRpb24gYXMgY29tcHV0ZWQgYnkgUmFuZCBJbmRleCBpcyBgciBhcmlgCgojIyMgQ29uY2x1c2lvbiAKCiogSW4gdGhpcyBzY2VuYXJpbyB3ZSBwZXJmb3JtZWQgYW4gaW50ZWdyYXRpdmUgc3VwZXJ2aXNlZCBjbHVzdGVyaW5nLiBOb3RlIHRoYXQsIHN1cnZDbHVzdCBjYW4gcGVyZm9ybSBjbHVzdGVyaW5nIG9uIGFzIG1hbnkgZGF0YXNldHMgYXMgcG9zc2libGUgYW5kIGNvbnNpc3RpbmcgYSBtaXggb2YgYmluYXJ5IGFuZCBjb250aW51b3VzIGRhdGEgdHlwZXMuIEluIHRoZSBwdWJsaXNoZWQgd29yaywgc3VydkNsdXN0IHBlcmZvcm1lZCBpbnRlZ3JhdGl2ZSBjbHVzdGVyaW5nIG9uIDYgZGF0YSB0eXBlcy4gCgoqIFRoZSBSYW5kIEluZGV4IGlzIGJldHdlZW4gc3VydkNsdXN0IHNvbHV0aW9uIGFuZCB0aGUgdHJ1dGggaXMgYHIgYXJpYCBtZWFuaW5nIGV2ZW4gdXBvbiBhZGRpdGlvbiBvZiBhIHdlYWtlciBkYXRhIHNldCwgYHN1cnZDbHVzdGAgaXMgYWJsZSB0byBwcmVkaWN0IGEgc29sdXRpb24gY2xvc2UgdG8gdHJ1dGggd2l0aG91dCBhZmZlY3RpbmcgaXRzIHBlcmZvcm1hbmNlIGluIHRoZSBwcmVzZW5jZSBvZiBub2lzZS4gCgojIyBTY2VuYXJpbyAzCgpJbiB0aGlzIGxhc3Qgc2NlbmFyaW8sIHdlIHNpbXVsYXRlIHR3byBkYXRhc2V0cyB3aXRoIDE3NSBzYW1wbGVzIGFuZCAyMDAgZmVhdHVyZXMgc3VjaCB0aGF0IC0gCgpEYXRhIHR5cGUgMSDigJMgc3Ryb25nIGNsdXN0ZXJzIGFuZCB3ZWFrIHN1cnZpdmFsIGFzc29jaWF0aW9uLgoKRGF0YXR5cGUgMiDigJMgd2VhayBjbHVzdGVycyBhbmQgc3Ryb25nIGNsdXN0ZXIgYXNzb2NpYXRpb24uIAoKYGBge3IsIGVjaG89RkFMU0V9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJpbWFnZXMvc2NlbmFyaW8zLnBuZyIpCmBgYAoKCmBgYHtyfQoKbWF0X3NjZW5hcmlvMyA8LSBsaXN0KCkKCiNkYXRhc2V0IDEKbWF0X3NjZW5hcmlvM1tbMV1dIDwtIHNpbXVsYXRlX2RhdGFfdHlwZShrPTMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtfc2l6ZSA9IGMoNTAsNTAsNzUpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX2ZlYXR1cmVzID0gMjAwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdSA9IGMoMCwtMS41LCAxLjUpLCBzZCA9IHJlcCgxLDMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBlcmNfaW5mb3JtID0gMC4xMCkKCiNkYXRhc2V0IDIKbWF0X3NjZW5hcmlvM1tbMl1dIDwtIHNpbXVsYXRlX2RhdGFfdHlwZShrPTQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtfc2l6ZSA9IGMoNTAsNTAsMzAsNDUpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX2ZlYXR1cmVzID0gMjAwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdSA9IGMoMCwtMC41LCAwLjUsIDEuNSksIHNkID0gcmVwKDEsNCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBlcmNfaW5mb3JtID0gMC4xMCkKI3NpbXVsYXRlIHN1cnZpdmFsIGRhdGEgCnN1cnZfc2NlbmFyaW8zIDwtIHNpbXVsYXRlX3N1cnZfZGF0KGs9NCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtfc2l6ZSA9IGMoNTAsNTAsMzAsNDUpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWVkX3N1cnZfdGltZXMgPSBjKDUuNSw0LDIuNSwxKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4X3N1cnZpdmFsID0gMTAsIG15X3NlZWQ9MTEyKQojdHJ1dGgKdHJ1ZV9zb2xuX3NjZW5hcmlvMyA8LSBkYXRhLmZyYW1lKHNhbXBsZXMgPSBwYXN0ZTAoIlMiLCAxOjE3NSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3RlciA9IGMocmVwKDEsNTApLCByZXAoMiw1MCksIHJlcCgzLDMwKSwgcmVwKDQsNDUpKSkKYGBgCgpIZXJlIHRoZSBzdXJ2aXZhbCBhc3NvY2lhdGlvbiBpcyBzdHJvbmdlciB3aXRoIGRhdGFzZXQyIGFuZCB3ZSBzaW11bGF0ZSBhIHN1cnZpdmFsIGRhdGEgd2l0aCA0IGNsdXN0ZXJzLiBCZWxvdyBpcyB0aGUgc2ltdWxhdGVkIHN1cnZpdmFsIGRpc3RyaWJ1dGlvbiAtIAoKYGBge3IsIGZpZy53aWR0aD00LCBmaWcuaGVpZ2h0PTR9CgpwcCA9IHN1cnZfc2NlbmFyaW8zICU+JQogICAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4odmFyID0gInNhbXBsZXMiKSAlPiUKICAgIGxlZnRfam9pbiguLCB0cnVlX3NvbG5fc2NlbmFyaW8zLCBieSA9InNhbXBsZXMiKSAlPiUKICAgIGdnc3VydnBsb3Qoc3VydmZpdChTdXJ2KHRpbWUsIGV2ZW50KSB+IGNsdXN0ZXIsIGRhdGEgPSAuKSwgZGF0YSA9IC4sIAogICAgICAgICAgICAgICBzdXJ2Lm1lZGlhbi5saW5lID0gInYiLCBwYWxldHRlID0gbXlfY29sX3BhbCkgKyAKICAgIGdndGl0bGUoInN1cnZpdmFsIGRpc3RyaWJ1dGlvbiBhY3Jvc3MgXG4gaz00IGNsdXN0ZXJzLCBzY2VuYXJpbzMiKSAKCnBwJHBsb3QgKyB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMSksCiAgICAgICAgICAgICAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPTYpKQpgYGAKClRoZSBtZWRpYW4gc3Vydml2YWwgcmF0ZXMgb2YgdGhlIHNpbXVsYXRlZCBzdXJ2aXZhbCBkYXRhc2V0IGZyb20gYHN1cnZmaXRgIG1vZGVsIGFyZSBiZWxvdy4gCgpgYGB7cn0KI21lZGlhbiBzdXJ2aXZhbCByYXRlIG1vZGVsIGZpdCAKZml0IDwtIHN1cnZfc2NlbmFyaW8zICU+JQogICAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4odmFyID0gInNhbXBsZXMiKSAlPiUKICAgIGxlZnRfam9pbiguLCB0cnVlX3NvbG5fc2NlbmFyaW8zLCBieSA9InNhbXBsZXMiKSAlPiUKICAgIHN1cnZmaXQoU3Vydih0aW1lLCBldmVudCkgfiBjbHVzdGVyLCBkYXRhID0gLikKCmZpdF9tZWRpYW4gPC0gc3VtbWFyeShmaXQpJHRhYmxlWywnbWVkaWFuJ10gCgpkYXRhLmZyYW1lKGNsdXN0ZXIgPSBnc3ViKCJjbHVzdGVyPSIsImMiLG5hbWVzKGZpdF9tZWRpYW4pKSwgCiAgICAgICAgICAgYG1lZGlhbiBzdXJ2aXZhbCh5cnMpYCA9IHJvdW5kKGZpdF9tZWRpYW4sMikpICU+JSAKICBndDo6Z3QoY2FwdGlvbiA9ICJtZWRpYW4gc3Vydml2YWwgdGltZXMgYXJlIGZyb20gYHN1cnZmaXRgIGZpdCIpCgpgYGAKCiMjIyBzdXJ2Q2x1c3QgcnVuIAoKU2ltaWxhciwgdG8gcHJldmlvdXMgc2VjdGlvbiwgY3Jvc3MgdmFsaWRhdGlvbiB3YXMgcGVyZm9ybWVkIGFuZCBwYXJhbGxlbGl6ZWQgZm9yIGVhY2ggay4gCgpgYGB7cn0KcmVnaXN0ZXJEb1BhcmFsbGVsKGNsIDwtIG1ha2VDbHVzdGVyKDYpKQoKcHRtIDwtIFN5cy50aW1lKCkKc2ltM19jdl9maXQgPC0gZm9yZWFjaChraz0yOjcpICVkb3BhciUgc2ltX2N2cm91bmRzKGtrLCBzaW1kYXQgPSBtYXRfc2NlbmFyaW8zLCBzaW1zdXJ2ZGF0ID0gc3Vydl9zY2VuYXJpbzMpCnB0bTIgPC0gU3lzLnRpbWUoKQoKc3RvcENsdXN0ZXIoY2wpCnR0ID0gcHRtMi1wdG0KYGBgCgpUaGUgY3Jvc3MgdmFsaWRhdGlvbiB0b29rIGByIHJvdW5kKGFzLmRvdWJsZSh0dCwgdW5pdHM9Im1pbnMiKSwyKWAgbWludXRlcyB0byBydW4uIAoKIyMjIFJlc3VsdHMgYW5kIGNob29zaW5nIGsKClRoZSBsb2dyYW5rIHRlc3Qgc3RhdGlzdGljIGZvciB0aGlzIHNjZW5hcmlvIGlzIG1heGltdW0gYXQgYGs9NGAgYW5kIHdlIHNlZSB0aGF0IHRoZSBTUFdTUyBjdXJ2ZSAiZWxib3dzIiBhdCBgaz00YC4gVGhlIGNyb3NzIHZhbGlkYXRpb24gcm91bmQgb2YgYGs9NGAgYWxzbyBkaWQgbm90IGhhdmUgYW55IGNsdXN0ZXIgc2l6ZXMgd2l0aCA8PTUgc2FtcGxlcy4gVGh1cywgd2Ugd2lsbCBjaG9vc2UgYGs9NGAgYXMgdGhlIG9wdGltYWwgay4gIAoKYGBge3J9CnNzX3N0YXRzIDwtIGdldFN0YXRzKHNpbTNfY3ZfZml0LCBraz03LCBjdnI9MTApCgpwX2xyIDwtIHBsb3RTdGF0c190aWR5KHNzX3N0YXRzJGxyLCBoaWdobGlnaHRfayA9IDQpCnBfbHIgPC0gcF9sciArIHlsYWIoImxvZ3JhbmsgdGVzdCBzdGF0aXN0aWMiKQoKcF9zcHdzcyA8LSBwbG90U3RhdHNfdGlkeShzc19zdGF0cyRzcHdzcywgaGlnaGxpZ2h0X2sgPSA0KQpwX3Nwd3NzIDwtIHBfc3B3c3MgKyB5bGFiKCJTUFdTUyIpCgpwX2NsdXNpemUgPC0gZGF0YS5mcmFtZShrID0gcGFzdGUwKCJrIiwyOjcpLCBjdl9sZXNzX3RoYW5fNSA9IHNzX3N0YXRzJGJhZC5zb2wpICU+JQogICAgZ2dwbG90KGFlcyh4PWssIHkgPSBjdl9sZXNzX3RoYW5fNSkpICsgZ2VvbV9wb2ludCgpICsgZ2VvbV9saW5lKGNvbCA9ICJza3libHVlIiwgZ3JvdXA9ImsiKSArIHRoZW1lX21pbmltYWwoKSArIHlsYWIoImN2IHNvbHV0aW9uIHdpdGggPD01IHNhbXBsZXMiKQpgYGAKCgpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9OH0KZ2dhcnJhbmdlKHBfbHIsIHBfc3B3c3MsIHBfY2x1c2l6ZSwgbnJvdz0yLCBuY29sPTIpCmBgYAoKCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQprNCA8LSBjdl92b3RpbmcoY3YuZml0ID0gc2ltM19jdl9maXQsCiAgICAgICAgICAgICAgICBkYXQuZGlzdCA9IGdldERpc3QoZGF0YXNldHMgPSBtYXRfc2NlbmFyaW8zLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1cnZkYXQgPSBzdXJ2X3NjZW5hcmlvMyksIAogICAgICAgICAgICAgICAgcGlja19rPTQpCmBgYAoKIyMjIyBrPTQKCkZyb20gdGhlIG1ldHJpY3MgYWJvdmUsIHdlIGNob29zZSBgaz00YCBhcyBvdXIgb3B0aW1hbCBrLiBXZSBjYW4gY29tcGFyZSB0aGlzIHdpdGggb3VyIHNpbXVsYXRlZCB0cnVlIHNvbHV0aW9uIC0gCgpgYGB7cn0KCnRhYiA8LSB0cnVlX3NvbG5fc2NlbmFyaW8zICU+JSAKICAgIGxlZnRfam9pbiguLCBkYXRhLmZyYW1lKHNpbV9zb2xuID0gazQsIHNhbXBsZXMgPSBwYXN0ZTAoIlMiLCAxOjE3NSkpLCAKICAgICAgICAgICAgICBieSA9ICJzYW1wbGVzIikKCnRhYiAlPiUgCiAgICBkcGx5cjo6c2VsZWN0KC1zYW1wbGVzKSAlPiUgCiAgICBkcGx5cjo6cmVuYW1lKCJ0cnV0aCIgPSBjbHVzdGVyKSAlPiUKICAgIGd0c3VtbWFyeTo6dGJsX3N1bW1hcnkoYnkgPSBzaW1fc29sbikgJT4lIG1vZGlmeV9oZWFkZXIobGFiZWwgPSAiKipzaW11bGF0ZWQgc29sdXRpb24qKiIpCgphcmkgPC0gcm91bmQobWNsdXN0OjphZGp1c3RlZFJhbmRJbmRleCh0YWIkc2ltX3NvbG4sIHRhYiRjbHVzdGVyKSwyKQpgYGAKClRoZSBjb25jb3JkYW5jZSBiZXR3ZWVuIHRydWUgYW5kIHN1cnZDbHVzdCBwcmVkaWN0ZWQgc29sdXRpb24gYXMgY29tcHV0ZWQgYnkgUmFuZCBJbmRleCBpcyBgciBhcmlgCgojIyMgQ29uY2x1c2lvbiAKCkluIHRoaXMgc2NlbmFyaW8gd2Ugb2JzZXJ2ZSB0aGF0IHN1cnZDbHVzdCB3YXMgYWJsZSB0byBhcnJpdmUgYXQgc29sdXRpb24gc2hvd2luZyBnb29kIGNvbmNvcmRhbmNlIHdpdGggdGhlIHRydXRoIGV2ZW4gd2hlbiB3ZWFrZXIgY2x1c3RlcnMgc2hvdyBiZXR0ZXIgYXNzb2NpYXRpb24gd2l0aCBzdXJ2aXZhbC4gCgojIERpc2N1c3Npb24gIAoKSGVyZSB3ZSBkZW1vbnN0cmF0ZWQgc3VydkNsdXN0LCBhIHN1cGVydmlzZWQgY2x1c3RlcmluZyBtZXRob2RvbG9neSB0aGF0IGNhbiBpbmNvcnBvcmF0ZSBzdXJ2aXZhbCBpbmZvcm1hdGlvbiBhcyB3ZWlnaHRzIGJ1dCBhbHNvIGludGVncmF0ZSBkYXRhIGZyb20gbXVsdGlwbGUgbW9kYWxpdGllcy4gV2UgbG9va2VkIGF0IHZhcmlvdXMgc2NlbmFyaW9zIGFuZCBoaWdobGlnaHRlZCBob3cgYHN1cnZDbHVzdGAgd2FzIGFibGUgdG8gcHJlZGljdCBzb2x1dGlvbnMgaW4gaGlnaCBjb25jb3JkYW5jZSB3aXRoIHRoZSB0cnVlIGNsYXNzIGxhYmVscyB0aGF0IHdlIHNpbXVsYXRlZC4gCgpIb3dldmVyLCB0aGVyZSB3ZXJlIGFkZGl0aW9uYWwgc2ltdWxhdGluZyBzdHJhdGVnaWVzIHRoYXQgb25lIGNvdWxkIGZ1cnRoZXIgYXBwbHkgLSAKCjEuIEF0IHByZXNlbnQgYSBjbHVzdGVyIGNvbnRhaW5zIGZlYXR1cmVzIHRoYXQgYXJlIGFzc29jaWF0ZWQgd2l0aCBzdXJ2aXZhbCBpbiBvbmUgZGlyZWN0aW9uIG9ubHkuIEluIG1vcmUgdGhvcm91Z2ggd29yayB0b3dhcmRzIHRoZSBmaW5hbCBwdWJsaWNhdGlvbiwgSSBzaW11bGF0ZWQgY2x1c3RlciBzdHJ1Y3R1cmVzIHRoYXQgaGFkIGZlYXR1cmVzIHRoYXQgaGFkIGEgbWl4IG9mIGZlYXR1cmVzIHRoYXQgY29udHJpYnV0ZWQgdG8gd29yc2UgYW5kIGJldHRlciBzdXJ2aXZhbCBhc3NvY2lhdGlvbi4gCgoyLiBTaW11bGF0aW5nIHN1cnZpdmFsIGRpc3RyaWJ1dGlvbiBvbiBhIHNtYWxsIHNhbXBsZSBzaXplIGlzIGNoYWxsZW5naW5nIGFuZCBtaWdodCB2aW9sYXRlIHRoZSBhc3N1bXB0aW9uIG9mIHByb3BvcnRpb25hbCBoYXphcmRzLiBUaGlzIGNvdWxkIGV4cGxhaW4gd2h5IGZvciBzb21lIHNjZW5hcmlvcyB3ZSBhcmUgbWlzY2xhc3NpZnlpbmcgc29tZSBzYW1wbGVzLiAKCjMuIGBzZXQuc2VlZGAgZnVuY3Rpb25hbGl0eSBwcm92aWRlZCB2aWEgYG15X3NlZWRgIHBhcmFtZXRlciBzaG91bGQgYmUgdXNlZCBjYXJlZnVsbHksIGFzIG9uZSBtaWdodCBiZSBkdXBsaWNhdGluZyBkYXRhIHdoaWxlIHNpbXVsYXRpbmcuIEZvciBleGFtcGxlLCBpZiB3ZSB3YW50IHRvIHNpbXVsYXRlIDYgY2x1c3RlcnMgYW5kIG91dCBvZiB3aGljaCAyIGFyZSBzaW1pbGFyIGluIHNpemUgYW5kIGRpc3RyaWJ1dGlvbiBidXQgbWlnaHQgdmFyeSBpbiB0ZXJtcyBvZiBzdXJ2aXZhbCwgYSBjYXNlIHdlIG9mdGVuIHNlZSB3aXRoIHJlYWwgZGF0YS4gCgpzdXJ2Q2x1c3QgY29udGludWVzIHRvIGJlIHVzZWQgYnkgbWFueSByZXNlYXJjaGVycyBpbiB0aGUgc2NpZW50aWZpYyBjb21tdW5pdHkuIAoKIyMgSXMgaW50ZWdyYXRpb24gYWx3YXlzIGJldHRlcj8gCgpzdXJ2Q2x1c3QgY2FuIGJlIHJ1biBpbmRpdmlkdWFsbHkgaW4gbXVsdGlwbGUgZGF0YXNldHMgb3IgaW4gYW4gaW50ZWdyYXRlZCBmYXNoaW9uIGFjcm9zcyBkaWZmZXJlbnQgbW9kYWxpdGllcyBwcm9maWxlZCBmb3IgY29uc3RhbnQgc3ViamVjdHMuIEFjY29yZGluZyB0byB0aGUgbmF0dXJlIGFuZCBxdWFsaXR5IG9mIHRoZXNlIGRhdGFzZXRzLCBvbmUgbWlnaHQgcGVyZm9ybSBiZXR0ZXIgb3Igd29yc2UgYnkgaW50ZWdyYXRpbmcgYWxsIHRoZSBhdmFpbGFibGUgaW5mb3JtYXRpb24uIFdlIHJhbiBzdXJ2Q2x1c3Qgb24gaW5kaXZpZHVhbCBkYXRhc2V0cyBmcm9tIHNjZW5hcmlvcyAyIGFuZCAzIGFuZCBjb21wYXJlIHRoZW0gd2l0aCB0aGUgaW50ZWdyYXRlZCBydW4gYXMgcmVwb3J0ZWQgYWJvdmUuIAoKYGBge3J9CgpyZWdpc3RlckRvUGFyYWxsZWwoY2wgPC0gbWFrZUNsdXN0ZXIoNikpCgpkYXQ8LWxpc3QoKQpkYXRbWzFdXSA8LSBtYXRfc2NlbmFyaW8yW1sxXV0Kc2ltMl9jdl9maXQxIDwtIGZvcmVhY2goa2s9Mjo3KSAlZG9wYXIlIHNpbV9jdnJvdW5kcyhraywgc2ltZGF0ID0gZGF0LCBzaW1zdXJ2ZGF0ID0gc3Vydl9zY2VuYXJpbzIpCgoKZGF0PC1saXN0KCkKZGF0W1sxXV0gPC0gbWF0X3NjZW5hcmlvMltbMl1dCnNpbTJfY3ZfZml0MiA8LSBmb3JlYWNoKGtrPTI6NykgJWRvcGFyJSBzaW1fY3Zyb3VuZHMoa2ssIHNpbWRhdCA9IGRhdCwgc2ltc3VydmRhdCA9IHN1cnZfc2NlbmFyaW8yKQoKCmRhdDwtbGlzdCgpCmRhdFtbMV1dIDwtIG1hdF9zY2VuYXJpbzNbWzFdXQpzaW0zX2N2X2ZpdDEgPC0gZm9yZWFjaChraz0yOjcpICVkb3BhciUgc2ltX2N2cm91bmRzKGtrLCBzaW1kYXQgPSBkYXQsIHNpbXN1cnZkYXQgPSBzdXJ2X3NjZW5hcmlvMykKCgpkYXQ8LWxpc3QoKQpkYXRbWzFdXSA8LSBtYXRfc2NlbmFyaW8zW1syXV0Kc2ltM19jdl9maXQyIDwtIGZvcmVhY2goa2s9Mjo3KSAlZG9wYXIlIHNpbV9jdnJvdW5kcyhraywgc2ltZGF0ID0gZGF0LCBzaW1zdXJ2ZGF0ID0gc3Vydl9zY2VuYXJpbzMpCgpzdG9wQ2x1c3RlcihjbCkKCmFsbF9zdGF0cyA8LSBwdXJycjo6bWFwKGMoInNpbTJfY3ZfZml0MSIsICJzaW0yX2N2X2ZpdDIiLCAic2ltM19jdl9maXQxIiwgInNpbTNfY3ZfZml0MiIpLCBmdW5jdGlvbih4KSBnZXRTdGF0cyhnZXQoeCksIGtrPTcsIGN2cj0xMCkpCgpuYW1lcyhhbGxfc3RhdHMpID0gYygic2ltMl9jdl9maXQxIiwgInNpbTJfY3ZfZml0MiIsICJzaW0zX2N2X2ZpdDEiLCAic2ltM19jdl9maXQyIikKCnNpbTJfc3RhdHMgPC0gZ2V0U3RhdHMoc2ltMl9jdl9maXQsIGtrPTcsIGN2ciA9IDEwKQpzaW0zX3N0YXRzIDwtIGdldFN0YXRzKHNpbTNfY3ZfZml0LCBraz03LCBjdnI9MTApCgpzY2VuYXJpbzJfaW50ZWcgPC0gbWFrZV9pbnRlZ3JhdGVkX2RhdGEoZGF0MSA9IGFsbF9zdGF0cyRzaW0yX2N2X2ZpdDEsIGRhdDIgPSBhbGxfc3RhdHMkc2ltMl9jdl9maXQyLCBpbnRlZyA9IHNpbTJfc3RhdHMpCgpzY2VuYXJpbzJfaW50ZWdfcGxvdHMgPC0gbWFrZV9pbnRlZ3JhdGVkX3Bsb3RzKHNjZW5hcmlvMl9pbnRlZykKCnBwIDwtIGdnYXJyYW5nZShwbG90bGlzdCA9IHNjZW5hcmlvMl9pbnRlZ19wbG90cywgbmNvbD0xKQpwcDIgPC0gYW5ub3RhdGVfZmlndXJlKHBwLCB0b3AgPSB0ZXh0X2dyb2IoIlNjZW5hcmlvMiIsIGZhY2UgPSAiYm9sZCIpKQoKCnNjZW5hcmlvM19pbnRlZyA8LSBtYWtlX2ludGVncmF0ZWRfZGF0YShkYXQxID0gYWxsX3N0YXRzJHNpbTNfY3ZfZml0MSwgZGF0MiA9IGFsbF9zdGF0cyRzaW0zX2N2X2ZpdDIsIGludGVnID0gc2ltM19zdGF0cykKCnNjZW5hcmlvM19pbnRlZ19wbG90cyA8LSBtYWtlX2ludGVncmF0ZWRfcGxvdHMoc2NlbmFyaW8zX2ludGVnKQoKcHAgPC0gZ2dhcnJhbmdlKHBsb3RsaXN0ID0gc2NlbmFyaW8zX2ludGVnX3Bsb3RzLCBuY29sPTEpCnBwMyA8LSBhbm5vdGF0ZV9maWd1cmUocHAsIHRvcCA9IHRleHRfZ3JvYigiU2NlbmFyaW8zIiwgZmFjZSA9ICJib2xkIikpCmBgYAoKYGBge3IsIGZpZy53aWR0aD05LCBmaWcuaGVpZ2h0PTl9CmdnYXJyYW5nZShwcDIsIHBwMykKYGBgCgpJbiB0aGUgcGxvdHMgYWJvdmUsIHdlIHBsb3QgdGhlIG1lZGlhbiB2YWx1ZSBhY3Jvc3MgY3Jvc3MgdmFsaWRhdGVkIHJvdW5kcyBmb3IgZWFjaCBrIGZvciB0aGF0IHBhcnRpY3VsYXIgbWV0cmljIChsb2dyYW5rLCBTUFdTUyBldGMuKS4gVGhlIGJhcnMgcmVwcmVzZW50IHRoZSAyNXRoIGFuZCA3NXRoIHF1YW50aWxlcyBvZiB0aGUgZGF0YS4gCgpJbiBzY2VuYXJpbyAyLCB3ZSBhZGRlZCBhIG5vaXN5IGRhdGEgdHlwZSBhcyBkYXRhc2V0IDIgYW5kIHdlIHNlZSB0aGF0IGRhdGFzZXQgMiBwZXJmb3JtcyB3b3JzZSB0aGFuIGludGVncmF0ZWQgYW5kIGRhdGFzZXQxLiBEYXRhc2V0MSBjYXJyaWVkIHRoZSBzdHJvbmcgY2x1c3RlciBkaXN0aW5jdGlvbiBhbmQgaXMgZG9pbmcgYXMgd2VsbCBhcyBpbnRlZ3JhdGVkIHN1cnZDbHVzdCBzb2x1dGlvbiBpbiB0ZXJtcyBvZiBwZWFrIGxvZ3JhbmsgYXQgYGs9M2AuIAoKQSBzaW1pbGFyIHRyZW5kIGlzIHNlZW4gZm9yIHNjZW5hcmlvIDMsIHdoZXJlIGRhdGFzZXQxIGhhZCBzdHJvbmcgY2x1c3RlciBzZXBhcmF0aW9uIGJ1dCBwb29yIHN1cnZpdmFsIGFzc29jaWF0aW9uIHdpdGggb3ZlcmFsbCBsb3cgbWVkaWFuIGxvZ3JhbmsgdmFsdWVzLiBXaGVyZWFzLCBpbnRlZ3JhdGVkIHNvbHV0aW9uIGFuZCBkYXRhc2V0MiBhcmUgcGVyZm9ybWluZyBzaW1pbGFyLiAKCk92ZXJhbGwsIHdlIHNlZSB0aGF0IGludGVncmF0ZWQgZGF0YSB0eXBlIHBlcmZvcm1zIGFzIHdlbGwgb3Igc2xpZ2h0bHkgYmV0dGVyIHRoYW4gdGhlIGRhdGFzZXQgY2FycnlpbmcgdGhlIHN0cm9uZyBhc3NvY2lhdGlvbiB3aXRoIHN1cnZpdmFsLiAKClteMV06IEFyb3JhLCBBLiwgZXQgYWwuIFBhbi1jYW5jZXIgaWRlbnRpZmljYXRpb24gb2YgY2xpbmljYWxseSByZWxldmFudCBnZW5vbWljIHN1YnR5cGVzIHVzaW5nIG91dGNvbWUtd2VpZ2h0ZWQgaW50ZWdyYXRpdmUgY2x1c3RlcmluZy4gR2Vub21lIE1lZGljaW5lIDEyLjEoMjAyMCk6IDEtMTMKClteMl06IEhvYWRsZXksIEthdGhlcmluZSBBLiwgZXQgYWwuICJDZWxsLW9mLW9yaWdpbiBwYXR0ZXJucyBkb21pbmF0ZSB0aGUgbW9sZWN1bGFyIGNsYXNzaWZpY2F0aW9uIG9mIDEwLDAwMCB0dW1vcnMgZnJvbSAzMyB0eXBlcyBvZiBjYW5jZXIuIiBDZWxsIDE3My4yICgyMDE4KTogMjkxLTMwNAoKW14zXTogSGFycmluZ3RvbiBEUCwgRmxlbWluZyBUUi4gQSBjbGFzcyBvZiByYW5rIHRlc3QgcHJvY2VkdXJlcyBmb3IgY2Vuc29yZWQgc3Vydml2YWwtZGF0YS4gQmlvbWV0cmlrYS4gMTk4Mjs2OTo1NTPigJM2Ni4gCgpbXjRdOiBUaWJzaGlyYW5pIFIsIFdhbHRoZXIgRywgSGFzdGllIFQuIEVzdGltYXRpbmcgdGhlIG51bWJlciBvZiBjbHVzdGVycyBpbiBhIGRhdGEgc2V0IHZpYSB0aGUgZ2FwIHN0YXRpc3RpYy4gSiBSb3kgU3RhdCBTb2MgQi4gMjAwMTs2Mzo0MTHigJMyMy4gCgojIFNlc3Npb24gSW5mbyAKCmBgYHtyIHNlc3Npb25JbmZvLCBjYWNoZT1GQUxTRX0KCiNlbnZpcm9uZW1udCBhbmQgcGFja2FnZSBkZXRhaWxzIApwYW5kZXI6OnBhbmRlcihzZXNzaW9uSW5mbygpKQoKewogICMgYWRkaXRpb25hbCBkZXRhaWxzIAogIGNhdCgiXG5cbiIpCiAgY2F0KHNwcmludGYoIioqU291cmNlOioqIColcyBpbiAlcyBwcm9qZWN0KiBcbiIsIAogICAgICAgICAgICAgIGtuaXRyOjpjdXJyZW50X2lucHV0KGRpcj1GQUxTRSksIHRyeUNhdGNoKHtyc3R1ZGlvYXBpOjpnZXRBY3RpdmVQcm9qZWN0KCl9LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlcnJvciA9IGZ1bmN0aW9uKGUpIHtOQX0pICkpCiAgY2F0KHNwcmludGYoIioqS25pdDoqKiAqJXMgYnkgJXMqIFxuIiwgZm9ybWF0KFN5cy50aW1lKCksICclWS0lbS0lZCAlSDolTTolUyVaJyksIFN5cy5pbmZvKClbWyJ1c2VyIl1dICkpCiAgY2F0KHRyeUNhdGNoKHtzcHJpbnRmKCIqKkdpdCByZW1vdGU6KiogKlslc10oJXMpe3RhcmdldD0nX2JsYW5rJ30qIFslc11cbiIsIGdpdDJyOjpyZW1vdGVfdXJsKCksIGdpdDJyOjpyZW1vdGVfdXJsKCksIGdpdDJyOjpjb21taXRzKGdpdDJyOjpyZXBvc2l0b3J5KCIuIiksIG49MSlbWzFdXVtbInNoYSJdXSl9LCBlcnJvciA9IGZ1bmN0aW9uKGUpIHtOVUxMfSkpCn0KCgoKYGBg